/*
 *
 * ===================================
 * 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/atm_socket.c,v 1.4 1999/08/28 00:48:37 peter Exp $
 *
 */

/*
 * Core ATM Services
 * -----------------
 *
 * ATM common socket protocol processing
 *
 */

#include <netatm/kern_include.h>

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


/*
 * Local functions
 */


/*
 * Local variables
 */
static struct sp_info		atm_pcb_pool = {
	"atm pcb pool",			/* si_name */
	sizeof(Atm_pcb),		/* si_blksiz */
	10,				/* si_blkcnt */
	100				/* si_maxallow */
};

static struct t_atm_cause	atm_sock_cause = {
	T_ATM_ITU_CODING,
	T_ATM_LOC_USER,
	T_ATM_CAUSE_UNSPECIFIED_NORMAL,
	{0, 0, 0, 0}
};


/*
 * Allocate resources for a new ATM socket
 *
 * Called at splnet.
 *
 * Arguments:
 *	so	pointer to socket
 *	send	socket send buffer maximum
 *	recv	socket receive buffer maximum
 *
 * Returns:
 *	0	attach successful
 *	errno	attach failed - reason indicated
 *
 */
int
atm_sock_attach(so, send, recv)
	struct socket	*so;
	u_long		send;
	u_long		recv;
{
	Atm_pcb		*atp = sotoatmpcb(so);
	int		err;

	/*
	 * Make sure initialization has happened
	 */
	if (!atm_init)
		atm_initialize();

	/*
	 * Make sure we're not already attached
	 */
	if (atp)
		return (EISCONN);

	/*
	 * Reserve socket buffer space, if not already done
	 */
	if ((so->so_snd.sb_hiwat == 0) || (so->so_rcv.sb_hiwat == 0)) {
		err = soreserve(so, send, recv);
		if (err)
			return (err);
	}

	/*
	 * Allocate and initialize our control block
	 */
	atp = (Atm_pcb *)atm_allocate(&atm_pcb_pool);
	if (atp == NULL)
		return (ENOMEM);

	atp->atp_socket = so;
	so->so_pcb = (caddr_t)atp;
	return (0);
}


/*
 * Detach from socket and free resources
 *
 * Called at splnet.
 *
 * Arguments:
 *	so	pointer to socket
 *
 * Returns:
 *	0	detach successful
 *	errno	detach failed - reason indicated
 *
 */
int
atm_sock_detach(so)
	struct socket	*so;
{
	Atm_pcb		*atp = sotoatmpcb(so);

	/*
	 * Make sure we're still attached
	 */
	if (atp == NULL)
		return (ENOTCONN);

	/*
	 * Terminate any (possibly pending) connection
	 */
	if (atp->atp_conn) {
		(void) atm_sock_disconnect(so);
	}

	/*
	 * Break links and free control blocks
	 */
	so->so_pcb = NULL;
	sofree(so);

	atm_free((caddr_t)atp);

	return (0);
}


/*
 * Bind local address to socket
 *
 * Called at splnet.
 *
 * Arguments:
 *	so	pointer to socket
 *	addr	pointer to protocol address
 *
 * Returns:
 *	0	request processed
 *	errno	error processing request - reason indicated
 *
 */
int
atm_sock_bind(so, addr)
	struct socket	*so;
	struct sockaddr	*addr;
{
	Atm_pcb			*atp = sotoatmpcb(so);
	Atm_attributes		attr;
	struct sockaddr_atm	*satm;
	struct t_atm_sap_addr	*sapadr;
	struct t_atm_sap_layer2	*sapl2;
	struct t_atm_sap_layer3	*sapl3;
	struct t_atm_sap_appl	*sapapl;

	/*
	 * Make sure we're still attached
	 */
	if (atp == NULL)
		return (ENOTCONN);

	/*
	 * Can't change local address once we've started connection process
	 */
	if (atp->atp_conn != NULL)
		return (EADDRNOTAVAIL);

	/*
	 * Validate requested local address
	 */
	satm = (struct sockaddr_atm *)addr;
	if (satm->satm_family != AF_ATM)
		return (EAFNOSUPPORT);

	sapadr = &satm->satm_addr.t_atm_sap_addr;
	if (sapadr->SVE_tag_addr == T_ATM_PRESENT) {
		if (sapadr->address_format == T_ATM_ENDSYS_ADDR) {
			if (sapadr->SVE_tag_selector != T_ATM_PRESENT)
				return (EINVAL);
		} else if (sapadr->address_format == T_ATM_E164_ADDR) {
			if (sapadr->SVE_tag_selector != T_ATM_ABSENT)
				return (EINVAL);
		} else
			return (EINVAL);
	} else if ((sapadr->SVE_tag_addr != T_ATM_ABSENT) &&
		   (sapadr->SVE_tag_addr != T_ATM_ANY))
		return (EINVAL);
	if (sapadr->address_length > ATM_ADDR_LEN)
		return (EINVAL);

	sapl2 = &satm->satm_addr.t_atm_sap_layer2;
	if (sapl2->SVE_tag == T_ATM_PRESENT) {
		if ((sapl2->ID_type != T_ATM_SIMPLE_ID) &&
		    (sapl2->ID_type != T_ATM_USER_ID))
			return (EINVAL);
	} else if ((sapl2->SVE_tag != T_ATM_ABSENT) &&
		   (sapl2->SVE_tag != T_ATM_ANY))
		return (EINVAL);

	sapl3 = &satm->satm_addr.t_atm_sap_layer3;
	if (sapl3->SVE_tag == T_ATM_PRESENT) {
		if ((sapl3->ID_type != T_ATM_SIMPLE_ID) &&
		    (sapl3->ID_type != T_ATM_IPI_ID) &&
		    (sapl3->ID_type != T_ATM_SNAP_ID) &&
		    (sapl3->ID_type != T_ATM_USER_ID))
			return (EINVAL);
	} else if ((sapl3->SVE_tag != T_ATM_ABSENT) &&
		   (sapl3->SVE_tag != T_ATM_ANY))
		return (EINVAL);

	sapapl = &satm->satm_addr.t_atm_sap_appl;
	if (sapapl->SVE_tag == T_ATM_PRESENT) {
		if ((sapapl->ID_type != T_ATM_ISO_APP_ID) &&
		    (sapapl->ID_type != T_ATM_USER_APP_ID) &&
		    (sapapl->ID_type != T_ATM_VENDOR_APP_ID))
			return (EINVAL);
	} else if ((sapapl->SVE_tag != T_ATM_ABSENT) &&
		   (sapapl->SVE_tag != T_ATM_ANY))
		return (EINVAL);

	/*
	 * Create temporary attributes list so that we can check out the
	 * new bind parameters before we modify the socket's values;
	 */
	attr = atp->atp_attr;
	attr.called.tag = sapadr->SVE_tag_addr;
	KM_COPY(&sapadr->address_format, &attr.called.addr, sizeof(Atm_addr));

	attr.blli.tag_l2 = sapl2->SVE_tag;
	if (sapl2->SVE_tag == T_ATM_PRESENT) {
		attr.blli.v.layer_2_protocol.ID_type = sapl2->ID_type;
		KM_COPY(&sapl2->ID, &attr.blli.v.layer_2_protocol.ID,
			sizeof(attr.blli.v.layer_2_protocol.ID));
	}

	attr.blli.tag_l3 = sapl3->SVE_tag;
	if (sapl3->SVE_tag == T_ATM_PRESENT) {
		attr.blli.v.layer_3_protocol.ID_type = sapl3->ID_type;
		KM_COPY(&sapl3->ID, &attr.blli.v.layer_3_protocol.ID,
			sizeof(attr.blli.v.layer_3_protocol.ID));
	}

	attr.bhli.tag = sapapl->SVE_tag;
	if (sapapl->SVE_tag == T_ATM_PRESENT) {
		attr.bhli.v.ID_type = sapapl->ID_type;
		KM_COPY(&sapapl->ID, &attr.bhli.v.ID,
			sizeof(attr.bhli.v.ID));
	}

	/*
	 * Make sure we have unique listening attributes
	 */
	if (atm_cm_match(&attr, NULL) != NULL)
		return (EADDRINUSE);

	/*
	 * Looks good, save new attributes
	 */
	atp->atp_attr = attr;

	return (0);
}


/*
 * Listen for incoming connections
 *
 * Called at splnet.
 *
 * Arguments:
 *	so	pointer to socket
 *	epp	pointer to endpoint definition structure
 *
 * Returns:
 *	0	request processed
 *	errno	error processing request - reason indicated
 *
 */
int
atm_sock_listen(so, epp)
	struct socket	*so;
	Atm_endpoint	*epp;
{
	Atm_pcb		*atp = sotoatmpcb(so);

	/*
	 * Make sure we're still attached
	 */
	if (atp == NULL)
		return (ENOTCONN);

	/*
	 * Start listening for incoming calls
	 */
	return (atm_cm_listen(epp, atp, &atp->atp_attr, &atp->atp_conn));
}


/*
 * Connect socket to peer
 *
 * Called at splnet.
 *
 * Arguments:
 *	so	pointer to socket
 *	addr	pointer to protocol address
 *	epp	pointer to endpoint definition structure
 *
 * Returns:
 *	0	request processed
 *	errno	error processing request - reason indicated
 *
 */
int
atm_sock_connect(so, addr, epp)
	struct socket	*so;
	struct sockaddr	*addr;
	Atm_endpoint	*epp;
{
	Atm_pcb		*atp = sotoatmpcb(so);
	struct sockaddr_atm	*satm;
	struct t_atm_sap_addr	*sapadr;
	struct t_atm_sap_layer2	*sapl2;
	struct t_atm_sap_layer3	*sapl3;
	struct t_atm_sap_appl	*sapapl;
	int		err;

	/*
	 * Make sure we're still attached
	 */
	if (atp == NULL)
		return (ENOTCONN);

	/*
	 * Validate requested peer address
	 */
	satm = (struct sockaddr_atm *)addr;
	if (satm->satm_family != AF_ATM)
		return (EAFNOSUPPORT);

	sapadr = &satm->satm_addr.t_atm_sap_addr;
	if (sapadr->SVE_tag_addr != T_ATM_PRESENT)
		return (EINVAL);
	if (sapadr->address_format == T_ATM_ENDSYS_ADDR) {
		if (sapadr->SVE_tag_selector != T_ATM_PRESENT)
			return (EINVAL);
	} else if (sapadr->address_format == T_ATM_E164_ADDR) {
		if (sapadr->SVE_tag_selector != T_ATM_ABSENT)
			return (EINVAL);
	} else if (sapadr->address_format == T_ATM_PVC_ADDR) {
		if (sapadr->SVE_tag_selector != T_ATM_ABSENT)
			return (EINVAL);
	} else
		return (EINVAL);
	if (sapadr->address_length > ATM_ADDR_LEN)
		return (EINVAL);

	sapl2 = &satm->satm_addr.t_atm_sap_layer2;
	if (sapl2->SVE_tag == T_ATM_PRESENT) {
		if ((sapl2->ID_type != T_ATM_SIMPLE_ID) &&
		    (sapl2->ID_type != T_ATM_USER_ID))
			return (EINVAL);
	} else if (sapl2->SVE_tag != T_ATM_ABSENT)
		return (EINVAL);

	sapl3 = &satm->satm_addr.t_atm_sap_layer3;
	if (sapl3->SVE_tag == T_ATM_PRESENT) {
		if ((sapl3->ID_type != T_ATM_SIMPLE_ID) &&
		    (sapl3->ID_type != T_ATM_IPI_ID) &&
		    (sapl3->ID_type != T_ATM_SNAP_ID) &&
		    (sapl3->ID_type != T_ATM_USER_ID))
			return (EINVAL);
	} else if (sapl3->SVE_tag != T_ATM_ABSENT)
		return (EINVAL);

	sapapl = &satm->satm_addr.t_atm_sap_appl;
	if (sapapl->SVE_tag == T_ATM_PRESENT) {
		if ((sapapl->ID_type != T_ATM_ISO_APP_ID) &&
		    (sapapl->ID_type != T_ATM_USER_APP_ID) &&
		    (sapapl->ID_type != T_ATM_VENDOR_APP_ID))
			return (EINVAL);
	} else if (sapapl->SVE_tag != T_ATM_ABSENT)
		return (EINVAL);

	/*
	 * Select an outgoing network interface
	 */
	if (atp->atp_attr.nif == NULL) {
		struct atm_pif	*pip;

		for (pip = atm_interface_head; pip != NULL;
						pip = pip->pif_next) {
			if (pip->pif_nif != NULL) {
				atp->atp_attr.nif = pip->pif_nif;
				break;
			}
		}
		if (atp->atp_attr.nif == NULL)
			return (ENXIO);
	}

	/*
	 * Set supplied connection attributes
	 */
	atp->atp_attr.called.tag = T_ATM_PRESENT;
	KM_COPY(&sapadr->address_format, &atp->atp_attr.called.addr,
			sizeof(Atm_addr));

	atp->atp_attr.blli.tag_l2 = sapl2->SVE_tag;
	if (sapl2->SVE_tag == T_ATM_PRESENT) {
		atp->atp_attr.blli.v.layer_2_protocol.ID_type = sapl2->ID_type;
		KM_COPY(&sapl2->ID, &atp->atp_attr.blli.v.layer_2_protocol.ID,
			sizeof(atp->atp_attr.blli.v.layer_2_protocol.ID));
	}

	atp->atp_attr.blli.tag_l3 = sapl3->SVE_tag;
	if (sapl3->SVE_tag == T_ATM_PRESENT) {
		atp->atp_attr.blli.v.layer_3_protocol.ID_type = sapl3->ID_type;
		KM_COPY(&sapl3->ID, &atp->atp_attr.blli.v.layer_3_protocol.ID,
			sizeof(atp->atp_attr.blli.v.layer_3_protocol.ID));
	}

	atp->atp_attr.bhli.tag = sapapl->SVE_tag;
	if (sapapl->SVE_tag == T_ATM_PRESENT) {
		atp->atp_attr.bhli.v.ID_type = sapapl->ID_type;
		KM_COPY(&sapapl->ID, &atp->atp_attr.bhli.v.ID,
			sizeof(atp->atp_attr.bhli.v.ID));
	}

	/*
	 * We're finally ready to initiate the ATM connection
	 */
	soisconnecting(so);
	atm_sock_stat.as_connreq[atp->atp_type]++;
	err = atm_cm_connect(epp, atp, &atp->atp_attr, &atp->atp_conn);
	if (err == 0) {
		/*
		 * Connection is setup
		 */
		atm_sock_stat.as_conncomp[atp->atp_type]++;
		soisconnected(so);

	} else if (err == EINPROGRESS) {
		/*
		 * We've got to wait for a connected event
		 */
		err = 0;

	} else {
		/*
		 * Call failed...
		 */
		atm_sock_stat.as_connfail[atp->atp_type]++;
		soisdisconnected(so);
	}

	return (err);
}


/*
 * Disconnect connected socket
 *
 * Called at splnet.
 *
 * Arguments:
 *	so	pointer to socket
 *
 * Returns:
 *	0	request processed
 *	errno	error processing request - reason indicated
 *
 */
int
atm_sock_disconnect(so)
	struct socket	*so;
{
	Atm_pcb		*atp = sotoatmpcb(so);
	struct t_atm_cause	*cause;
	int		err;

	/*
	 * Make sure we're still attached
	 */
	if (atp == NULL)
		return (ENOTCONN);

	/*
	 * Release the ATM connection
	 */
	if (atp->atp_conn) {
		if (atp->atp_attr.cause.tag == T_ATM_PRESENT)
			cause = &atp->atp_attr.cause.v;
		else
			cause = &atm_sock_cause;
		err = atm_cm_release(atp->atp_conn, cause);
		if (err)
			log(LOG_ERR, "atm_sock_disconnect: release fail (%d)\n",
				err);
		atm_sock_stat.as_connrel[atp->atp_type]++;
		atp->atp_conn = NULL;
	}

	soisdisconnected(so);

	return (0);
}


/*
 * Retrieve local socket address
 *
 * Called at splnet.
 *
 * Arguments:
 *	so	pointer to socket
 *	addr	pointer to pointer to contain protocol address
 *
 * Returns:
 *	0	request processed
 *	errno	error processing request - reason indicated
 *
 */
int
atm_sock_sockaddr(so, addr)
	struct socket	*so;
	struct sockaddr	**addr;
{
	struct sockaddr_atm	*satm;
	struct t_atm_sap_addr	*saddr;
	Atm_pcb		*atp = sotoatmpcb(so);

	/*
	 * Return local interface address, if known
	 */
	satm = KM_ALLOC(sizeof *satm, M_SONAME, M_WAITOK);
	if (satm == NULL)
		return (ENOMEM);

	KM_ZERO(satm, sizeof(*satm));
	satm->satm_family = AF_ATM;
#if (defined(BSD) && (BSD >= 199103))
	satm->satm_len = sizeof(*satm);
#endif

	saddr = &satm->satm_addr.t_atm_sap_addr;
	if (atp->atp_attr.nif && atp->atp_attr.nif->nif_pif->pif_siginst) {
		saddr->SVE_tag_addr = T_ATM_PRESENT;
		ATM_ADDR_SEL_COPY(
			&atp->atp_attr.nif->nif_pif->pif_siginst->si_addr,
			atp->atp_attr.nif->nif_sel, saddr);
		if (saddr->address_format == T_ATM_ENDSYS_ADDR)
			saddr->SVE_tag_selector = T_ATM_PRESENT;
		else
			saddr->SVE_tag_selector = T_ATM_ABSENT;
	} else {
		saddr->SVE_tag_addr = T_ATM_ABSENT;
		saddr->SVE_tag_selector = T_ATM_ABSENT;
		saddr->address_format = T_ATM_ABSENT;
	}
	satm->satm_addr.t_atm_sap_layer2.SVE_tag = T_ATM_ABSENT;
	satm->satm_addr.t_atm_sap_layer3.SVE_tag = T_ATM_ABSENT;
	satm->satm_addr.t_atm_sap_appl.SVE_tag = T_ATM_ABSENT;

	*addr = (struct sockaddr *)satm;
	return (0);
}


/*
 * Retrieve peer socket address
 *
 * Called at splnet.
 *
 * Arguments:
 *	so	pointer to socket
 *	addr	pointer to pointer to contain protocol address
 *
 * Returns:
 *	0	request processed
 *	errno	error processing request - reason indicated
 *
 */
int
atm_sock_peeraddr(so, addr)
	struct socket	*so;
	struct sockaddr	**addr;
{
	struct sockaddr_atm	*satm;
	struct t_atm_sap_addr	*saddr;
	Atm_pcb		*atp = sotoatmpcb(so);
	Atm_connvc	*cvp;

	/*
	 * Return remote address, if known
	 */
	satm = KM_ALLOC(sizeof *satm, M_SONAME, M_WAITOK);
	if (satm == NULL)
		return (ENOMEM);

	KM_ZERO(satm, sizeof(*satm));
	satm->satm_family = AF_ATM;
#if (defined(BSD) && (BSD >= 199103))
	satm->satm_len = sizeof(*satm);
#endif

	saddr = &satm->satm_addr.t_atm_sap_addr;
	if (so->so_state & SS_ISCONNECTED) {
		cvp = atp->atp_conn->co_connvc;
		saddr->SVE_tag_addr = T_ATM_PRESENT;
		if (cvp->cvc_flags & CVCF_CALLER) {
			ATM_ADDR_COPY(&cvp->cvc_attr.called.addr, saddr);
		} else {
			if (cvp->cvc_attr.calling.tag == T_ATM_PRESENT) {
				ATM_ADDR_COPY(&cvp->cvc_attr.calling.addr,
							saddr);
			} else {
				saddr->SVE_tag_addr = T_ATM_ABSENT;
				saddr->address_format = T_ATM_ABSENT;
			}
		}
		if (saddr->address_format == T_ATM_ENDSYS_ADDR)
			saddr->SVE_tag_selector = T_ATM_PRESENT;
		else
			saddr->SVE_tag_selector = T_ATM_ABSENT;
	} else {
		saddr->SVE_tag_addr = T_ATM_ABSENT;
		saddr->SVE_tag_selector = T_ATM_ABSENT;
		saddr->address_format = T_ATM_ABSENT;
	}
	satm->satm_addr.t_atm_sap_layer2.SVE_tag = T_ATM_ABSENT;
	satm->satm_addr.t_atm_sap_layer3.SVE_tag = T_ATM_ABSENT;
	satm->satm_addr.t_atm_sap_appl.SVE_tag = T_ATM_ABSENT;

	*addr = (struct sockaddr *)satm;
	return (0);
}


/*
 * Common setsockopt processing
 *
 * Called at splnet.
 *
 * Arguments:
 *	so	pointer to socket
 *	sopt	pointer to socket option info
 *	atp	pointer to ATM PCB
 *
 * Returns:
 *	0 	request processed
 *	errno	error processing request - reason indicated
 *
 */
int
atm_sock_setopt(so, sopt, atp)
	struct socket	*so;
	struct sockopt	*sopt;
	Atm_pcb		*atp;
{
	int	err = 0;
	union {
		struct t_atm_aal5	aal5;
		struct t_atm_traffic	trf;
		struct t_atm_bearer	brr;
		struct t_atm_bhli	bhl;
		struct t_atm_blli	bll;
		Atm_addr		addr;
		struct t_atm_cause	cau;
		struct t_atm_qos	qos;
		struct t_atm_transit	trn;
		struct t_atm_net_intf	nif;
		struct t_atm_llc	llc;
		struct t_atm_app_name	appn;
	} p;

#define	MAXVAL(bits)	((1 << bits) - 1)
#define	MAXMASK(bits)	(~MAXVAL(bits))

	switch (sopt->sopt_name) {

	case T_ATM_AAL5:
		err = sooptcopyin(sopt, &p.aal5, sizeof p.aal5, sizeof p.aal5);
		if (err)
			break;
		if ((p.aal5.forward_max_SDU_size != T_ATM_ABSENT) &&
		    (p.aal5.forward_max_SDU_size & MAXMASK(16)))
			return (EINVAL);
		if ((p.aal5.backward_max_SDU_size != T_ATM_ABSENT) &&
		    (p.aal5.backward_max_SDU_size & MAXMASK(16)))
			return (EINVAL);
		if ((p.aal5.SSCS_type != T_ATM_ABSENT) &&
		    (p.aal5.SSCS_type != T_ATM_NULL) &&
		    (p.aal5.SSCS_type != T_ATM_SSCS_SSCOP_REL) &&
		    (p.aal5.SSCS_type != T_ATM_SSCS_SSCOP_UNREL) &&
		    (p.aal5.SSCS_type != T_ATM_SSCS_FR))
			return (EINVAL);

		if ((p.aal5.forward_max_SDU_size == T_ATM_ABSENT) &&
		    (p.aal5.backward_max_SDU_size == T_ATM_ABSENT) &&
		    (p.aal5.SSCS_type == T_ATM_ABSENT))
			atp->atp_attr.aal.tag = T_ATM_ABSENT;
		else {
			atp->atp_attr.aal.tag = T_ATM_PRESENT;
			atp->atp_attr.aal.type = ATM_AAL5;
			atp->atp_attr.aal.v.aal5 = p.aal5;
		}
		break;

	case T_ATM_TRAFFIC:
		err = sooptcopyin(sopt, &p.trf, sizeof p.trf, sizeof p.trf);
		if (err)
			break;
		if ((p.trf.forward.PCR_high_priority != T_ATM_ABSENT) &&
		    (p.trf.forward.PCR_high_priority & MAXMASK(24)))
			return (EINVAL);
		if (p.trf.forward.PCR_all_traffic & MAXMASK(24))
			return (EINVAL);
		if ((p.trf.forward.SCR_high_priority != T_ATM_ABSENT) &&
		    (p.trf.forward.SCR_high_priority & MAXMASK(24)))
			return (EINVAL);
		if ((p.trf.forward.SCR_all_traffic != T_ATM_ABSENT) &&
		    (p.trf.forward.SCR_all_traffic & MAXMASK(24)))
			return (EINVAL);
		if ((p.trf.forward.MBS_high_priority != T_ATM_ABSENT) &&
		    (p.trf.forward.MBS_high_priority & MAXMASK(24)))
			return (EINVAL);
		if ((p.trf.forward.MBS_all_traffic != T_ATM_ABSENT) &&
		    (p.trf.forward.MBS_all_traffic & MAXMASK(24)))
			return (EINVAL);
		if ((p.trf.forward.tagging != T_YES) &&
		    (p.trf.forward.tagging != T_NO))
			return (EINVAL);

		if ((p.trf.backward.PCR_high_priority != T_ATM_ABSENT) &&
		    (p.trf.backward.PCR_high_priority & MAXMASK(24)))
			return (EINVAL);
		if (p.trf.backward.PCR_all_traffic & MAXMASK(24))
			return (EINVAL);
		if ((p.trf.backward.SCR_high_priority != T_ATM_ABSENT) &&
		    (p.trf.backward.SCR_high_priority & MAXMASK(24)))
			return (EINVAL);
		if ((p.trf.backward.SCR_all_traffic != T_ATM_ABSENT) &&
		    (p.trf.backward.SCR_all_traffic & MAXMASK(24)))
			return (EINVAL);
		if ((p.trf.backward.MBS_high_priority != T_ATM_ABSENT) &&
		    (p.trf.backward.MBS_high_priority & MAXMASK(24)))
			return (EINVAL);
		if ((p.trf.backward.MBS_all_traffic != T_ATM_ABSENT) &&
		    (p.trf.backward.MBS_all_traffic & MAXMASK(24)))
			return (EINVAL);
		if ((p.trf.backward.tagging != T_YES) &&
		    (p.trf.backward.tagging != T_NO))
			return (EINVAL);
		if ((p.trf.best_effort != T_YES) &&
		    (p.trf.best_effort != T_NO))
			return (EINVAL);

		atp->atp_attr.traffic.tag = T_ATM_PRESENT;
		atp->atp_attr.traffic.v = p.trf;
		break;

	case T_ATM_BEARER_CAP:
		err = sooptcopyin(sopt, &p.brr, sizeof p.brr, sizeof p.brr);
		if (err)
			break;
		if ((p.brr.bearer_class != T_ATM_CLASS_A) &&
		    (p.brr.bearer_class != T_ATM_CLASS_C) &&
		    (p.brr.bearer_class != T_ATM_CLASS_X))
			return (EINVAL);
		if ((p.brr.traffic_type != T_ATM_NULL) &&
		    (p.brr.traffic_type != T_ATM_CBR) &&
		    (p.brr.traffic_type != T_ATM_VBR))
			return (EINVAL);
		if ((p.brr.timing_requirements != T_ATM_NULL) &&
		    (p.brr.timing_requirements != T_ATM_END_TO_END) &&
		    (p.brr.timing_requirements != T_ATM_NO_END_TO_END))
			return (EINVAL);
		if ((p.brr.clipping_susceptibility != T_NO) &&
		    (p.brr.clipping_susceptibility != T_YES))
			return (EINVAL);
		if ((p.brr.connection_configuration != T_ATM_1_TO_1) &&
		    (p.brr.connection_configuration != T_ATM_1_TO_MANY))
			return (EINVAL);

		atp->atp_attr.bearer.tag = T_ATM_PRESENT;
		atp->atp_attr.bearer.v = p.brr;
		break;

	case T_ATM_BHLI:
		err = sooptcopyin(sopt, &p.bhl, sizeof p.bhl, sizeof p.bhl);
		if (err)
			break;
		if ((p.bhl.ID_type != T_ATM_ABSENT) &&
		    (p.bhl.ID_type != T_ATM_ISO_APP_ID) &&
		    (p.bhl.ID_type != T_ATM_USER_APP_ID) &&
		    (p.bhl.ID_type != T_ATM_VENDOR_APP_ID))
			return (EINVAL);

		if (p.bhl.ID_type == T_ATM_ABSENT)
			atp->atp_attr.bhli.tag = T_ATM_ABSENT;
		else {
			atp->atp_attr.bhli.tag = T_ATM_PRESENT;
			atp->atp_attr.bhli.v = p.bhl;
		}
		break;

	case T_ATM_BLLI:
		err = sooptcopyin(sopt, &p.bll, sizeof p.bll, sizeof p.bll);
		if (err)
			break;
		if ((p.bll.layer_2_protocol.ID_type != T_ATM_ABSENT) &&
		    (p.bll.layer_2_protocol.ID_type != T_ATM_SIMPLE_ID) &&
		    (p.bll.layer_2_protocol.ID_type != T_ATM_USER_ID))
			return (EINVAL);
		if ((p.bll.layer_2_protocol.mode != T_ATM_ABSENT) &&
		    (p.bll.layer_2_protocol.mode != T_ATM_BLLI_NORMAL_MODE) &&
		    (p.bll.layer_2_protocol.mode != T_ATM_BLLI_EXTENDED_MODE))
			return (EINVAL);
		if ((p.bll.layer_2_protocol.window_size != T_ATM_ABSENT) &&
		    (p.bll.layer_2_protocol.window_size < 1))
			return (EINVAL);

		if ((p.bll.layer_3_protocol.ID_type != T_ATM_ABSENT) &&
		    (p.bll.layer_3_protocol.ID_type != T_ATM_SIMPLE_ID) &&
		    (p.bll.layer_3_protocol.ID_type != T_ATM_IPI_ID) &&
		    (p.bll.layer_3_protocol.ID_type != T_ATM_SNAP_ID) &&
		    (p.bll.layer_3_protocol.ID_type != T_ATM_USER_ID))
			return (EINVAL);
		if ((p.bll.layer_3_protocol.mode != T_ATM_ABSENT) &&
		    (p.bll.layer_3_protocol.mode != T_ATM_BLLI_NORMAL_MODE) &&
		    (p.bll.layer_3_protocol.mode != T_ATM_BLLI_EXTENDED_MODE))
			return (EINVAL);
		if ((p.bll.layer_3_protocol.packet_size != T_ATM_ABSENT) &&
		    (p.bll.layer_3_protocol.packet_size & MAXMASK(4)))
			return (EINVAL);
		if ((p.bll.layer_3_protocol.window_size != T_ATM_ABSENT) &&
		    (p.bll.layer_3_protocol.window_size < 1))
			return (EINVAL);

		if (p.bll.layer_2_protocol.ID_type == T_ATM_ABSENT) 
			atp->atp_attr.blli.tag_l2 = T_ATM_ABSENT;
		else
			atp->atp_attr.blli.tag_l2 = T_ATM_PRESENT;

		if (p.bll.layer_3_protocol.ID_type == T_ATM_ABSENT) 
			atp->atp_attr.blli.tag_l3 = T_ATM_ABSENT;
		else
			atp->atp_attr.blli.tag_l3 = T_ATM_PRESENT;

		if ((atp->atp_attr.blli.tag_l2 == T_ATM_PRESENT) ||
		    (atp->atp_attr.blli.tag_l3 == T_ATM_PRESENT))
			atp->atp_attr.blli.v = p.bll;
		break;

	case T_ATM_DEST_ADDR:
		err = sooptcopyin(sopt, &p.addr, sizeof p.addr, sizeof p.addr);
		if (err)
			break;
		if ((p.addr.address_format != T_ATM_ENDSYS_ADDR) &&
		    (p.addr.address_format != T_ATM_E164_ADDR))
			return (EINVAL);
		if (p.addr.address_length > ATM_ADDR_LEN)
			return (EINVAL);

		atp->atp_attr.called.tag = T_ATM_PRESENT;
		atp->atp_attr.called.addr = p.addr;
		break;

	case T_ATM_DEST_SUB:
		err = sooptcopyin(sopt, &p.addr, sizeof p.addr, sizeof p.addr);
		if (err)
			break;
		if ((p.addr.address_format != T_ATM_ABSENT) &&
		    (p.addr.address_format != T_ATM_NSAP_ADDR))
			return (EINVAL);
		if (p.addr.address_length > ATM_ADDR_LEN)
			return (EINVAL);

		/* T_ATM_DEST_ADDR controls tag */
		atp->atp_attr.called.subaddr = p.addr;
		break;

	case T_ATM_ORIG_ADDR:
		return (EACCES);

	case T_ATM_ORIG_SUB:
		return (EACCES);

	case T_ATM_CALLER_ID:
		return (EACCES);

	case T_ATM_CAUSE:
		err = sooptcopyin(sopt, &p.cau, sizeof p.cau, sizeof p.cau);
		if (err)
			break;
		if ((p.cau.coding_standard != T_ATM_ABSENT) &&
		    (p.cau.coding_standard != T_ATM_ITU_CODING) &&
		    (p.cau.coding_standard != T_ATM_NETWORK_CODING))
			return (EINVAL);
		if ((p.cau.location != T_ATM_LOC_USER) &&
		    (p.cau.location != T_ATM_LOC_LOCAL_PRIVATE_NET) &&
		    (p.cau.location != T_ATM_LOC_LOCAL_PUBLIC_NET) &&
		    (p.cau.location != T_ATM_LOC_TRANSIT_NET) &&
		    (p.cau.location != T_ATM_LOC_REMOTE_PUBLIC_NET) &&
		    (p.cau.location != T_ATM_LOC_REMOTE_PRIVATE_NET) &&
		    (p.cau.location != T_ATM_LOC_INTERNATIONAL_NET) &&
		    (p.cau.location != T_ATM_LOC_BEYOND_INTERWORKING))
			return (EINVAL);

		if (p.cau.coding_standard == T_ATM_ABSENT)
			atp->atp_attr.cause.tag = T_ATM_ABSENT;
		else {
			atp->atp_attr.cause.tag = T_ATM_PRESENT;
			atp->atp_attr.cause.v = p.cau;
		}
		break;

	case T_ATM_QOS:
		err = sooptcopyin(sopt, &p.qos, sizeof p.qos, sizeof p.qos);
		if (err)
			break;
		if ((p.qos.coding_standard != T_ATM_ABSENT) &&
		    (p.qos.coding_standard != T_ATM_ITU_CODING) &&
		    (p.qos.coding_standard != T_ATM_NETWORK_CODING))
			return (EINVAL);
		if ((p.qos.forward.qos_class != T_ATM_QOS_CLASS_0) &&
		    (p.qos.forward.qos_class != T_ATM_QOS_CLASS_1) &&
		    (p.qos.forward.qos_class != T_ATM_QOS_CLASS_2) &&
		    (p.qos.forward.qos_class != T_ATM_QOS_CLASS_3) &&
		    (p.qos.forward.qos_class != T_ATM_QOS_CLASS_4))
			return (EINVAL);
		if ((p.qos.backward.qos_class != T_ATM_QOS_CLASS_0) &&
		    (p.qos.backward.qos_class != T_ATM_QOS_CLASS_1) &&
		    (p.qos.backward.qos_class != T_ATM_QOS_CLASS_2) &&
		    (p.qos.backward.qos_class != T_ATM_QOS_CLASS_3) &&
		    (p.qos.backward.qos_class != T_ATM_QOS_CLASS_4))
			return (EINVAL);

		if (p.qos.coding_standard == T_ATM_ABSENT)
			atp->atp_attr.qos.tag = T_ATM_ABSENT;
		else {
			atp->atp_attr.qos.tag = T_ATM_PRESENT;
			atp->atp_attr.qos.v = p.qos;
		}
		break;

	case T_ATM_TRANSIT:
		err = sooptcopyin(sopt, &p.trn, sizeof p.trn, sizeof p.trn);
		if (err)
			break;
		if (p.trn.length > T_ATM_MAX_NET_ID)
			return (EINVAL);

		if (p.trn.length == 0)
			atp->atp_attr.transit.tag = T_ATM_ABSENT;
		else {
			atp->atp_attr.transit.tag = T_ATM_PRESENT;
			atp->atp_attr.transit.v = p.trn;
		}
		break;

	case T_ATM_ADD_LEAF:
		return (EPROTONOSUPPORT);	/* XXX */

	case T_ATM_DROP_LEAF:
		return (EPROTONOSUPPORT);	/* XXX */

	case T_ATM_NET_INTF:
		err = sooptcopyin(sopt, &p.nif, sizeof p.nif, sizeof p.nif);
		if (err)
			break;

		atp->atp_attr.nif = atm_nifname(p.nif.net_intf);
		if (atp->atp_attr.nif == NULL)
			return (ENXIO);
		break;

	case T_ATM_LLC:
		err = sooptcopyin(sopt, &p.llc, sizeof p.llc, sizeof p.llc);
		if (err)
			break;
		if ((p.llc.llc_len < T_ATM_LLC_MIN_LEN) ||
		    (p.llc.llc_len > T_ATM_LLC_MAX_LEN))
			return (EINVAL);

		atp->atp_attr.llc.tag = T_ATM_PRESENT;
		atp->atp_attr.llc.v = p.llc;
		break;

	case T_ATM_APP_NAME:
		err = sooptcopyin(sopt, &p.appn, sizeof p.appn, sizeof p.appn);
		if (err)
			break;

		strncpy(atp->atp_name, p.appn.app_name, T_ATM_APP_NAME_LEN);
		break;

	default:
		return (ENOPROTOOPT);
	}

	return (err);
}


/*
 * Common getsockopt processing
 *
 * Called at splnet.
 *
 * Arguments:
 *	so	pointer to socket
 *	sopt	pointer to socket option info
 *	atp	pointer to ATM PCB
 *
 * Returns:
 *	0 	request processed
 *	errno	error processing request - reason indicated
 *
 */
int
atm_sock_getopt(so, sopt, atp)
	struct socket	*so;
	struct sockopt	*sopt;
	Atm_pcb		*atp;
{
	Atm_attributes	*ap;

	/*
	 * If socket is connected, return attributes for the VCC in use,
	 * otherwise just return what the user has setup so far.
	 */
	if (so->so_state & SS_ISCONNECTED)
		ap = &atp->atp_conn->co_connvc->cvc_attr;
	else
		ap = &atp->atp_attr;

	switch (sopt->sopt_name) {

	case T_ATM_AAL5:
		if ((ap->aal.tag == T_ATM_PRESENT) &&
		    (ap->aal.type == ATM_AAL5)) {
			return (sooptcopyout(sopt, &ap->aal.v.aal5,
					sizeof ap->aal.v.aal5));
		} else {
			return (ENOENT);
		}
		break;

	case T_ATM_TRAFFIC:
		if (ap->traffic.tag == T_ATM_PRESENT) {
			return (sooptcopyout(sopt, &ap->traffic.v,
					sizeof ap->traffic.v));
		} else {
			return (ENOENT);
		}
		break;

	case T_ATM_BEARER_CAP:
		if (ap->bearer.tag == T_ATM_PRESENT) {
			return (sooptcopyout(sopt, &ap->bearer.v,
					sizeof ap->bearer.v));
		} else {
			return (ENOENT);
		}
		break;

	case T_ATM_BHLI:
		if (ap->bhli.tag == T_ATM_PRESENT) {
			return (sooptcopyout(sopt, &ap->bhli.v,
					sizeof ap->bhli.v));
		} else {
			return (ENOENT);
		}
		break;

	case T_ATM_BLLI:
		if ((ap->blli.tag_l2 == T_ATM_PRESENT) ||
		    (ap->blli.tag_l3 == T_ATM_PRESENT)) {
			return (sooptcopyout(sopt, &ap->blli.v,
					sizeof ap->blli.v));
		} else {
			return (ENOENT);
		}
		break;

	case T_ATM_DEST_ADDR:
		if (ap->called.tag == T_ATM_PRESENT) {
			return (sooptcopyout(sopt, &ap->called.addr,
					sizeof ap->called.addr));
		} else {
			return (ENOENT);
		}
		break;

	case T_ATM_DEST_SUB:
		if (ap->called.tag == T_ATM_PRESENT) {
			return (sooptcopyout(sopt, &ap->called.subaddr,
					sizeof ap->called.subaddr));
		} else {
			return (ENOENT);
		}
		break;

	case T_ATM_ORIG_ADDR:
		if (ap->calling.tag == T_ATM_PRESENT) {
			return (sooptcopyout(sopt, &ap->calling.addr,
					sizeof ap->calling.addr));
		} else {
			return (ENOENT);
		}
		break;

	case T_ATM_ORIG_SUB:
		if (ap->calling.tag == T_ATM_PRESENT) {
			return (sooptcopyout(sopt, &ap->calling.subaddr,
					sizeof ap->calling.subaddr));
		} else {
			return (ENOENT);
		}
		break;

	case T_ATM_CALLER_ID:
		if (ap->calling.tag == T_ATM_PRESENT) {
			return (sooptcopyout(sopt, &ap->calling.cid,
					sizeof ap->calling.cid));
		} else {
			return (ENOENT);
		}
		break;

	case T_ATM_CAUSE:
		if (ap->cause.tag == T_ATM_PRESENT) {
			return (sooptcopyout(sopt, &ap->cause.v,
					sizeof ap->cause.v));
		} else {
			return (ENOENT);
		}
		break;

	case T_ATM_QOS:
		if (ap->qos.tag == T_ATM_PRESENT) {
			return (sooptcopyout(sopt, &ap->qos.v,
					sizeof ap->qos.v));
		} else {
			return (ENOENT);
		}
		break;

	case T_ATM_TRANSIT:
		if (ap->transit.tag == T_ATM_PRESENT) {
			return (sooptcopyout(sopt, &ap->transit.v,
					sizeof ap->transit.v));
		} else {
			return (ENOENT);
		}
		break;

	case T_ATM_LEAF_IND:
		return (EPROTONOSUPPORT);	/* XXX */

	case T_ATM_NET_INTF:
		if (ap->nif) {
			struct t_atm_net_intf	netif;
			struct ifnet		*ifp;

			ifp = &ap->nif->nif_if;
			(void) snprintf(netif.net_intf, sizeof(netif.net_intf),
			    "%s%d", ifp->if_name, ifp->if_unit);
			return (sooptcopyout(sopt, &netif,
					sizeof netif));
		} else {
			return (ENOENT);
		}
		break;

	case T_ATM_LLC:
		if (ap->llc.tag == T_ATM_PRESENT) {
			return (sooptcopyout(sopt, &ap->llc.v,
					sizeof ap->llc.v));
		} else {
			return (ENOENT);
		}
		break;

	default:
		return (ENOPROTOOPT);
	}

	return (0);
}


/*
 * Process Socket VCC Connected Notification
 *
 * Arguments:
 *	toku	owner's connection token (atm_pcb protocol block)
 *
 * Returns:
 *	none
 *
 */
void
atm_sock_connected(toku)
	void		*toku;
{
	Atm_pcb		*atp = (Atm_pcb *)toku;

	/*
	 * Connection is setup
	 */
	atm_sock_stat.as_conncomp[atp->atp_type]++;
	soisconnected(atp->atp_socket);
}


/*
 * Process Socket VCC Cleared Notification
 *
 * Arguments:
 *	toku	owner's connection token (atm_pcb protocol block)
 *	cause	pointer to cause code
 *
 * Returns:
 *	none
 *
 */
void
atm_sock_cleared(toku, cause)
	void		*toku;
	struct t_atm_cause	*cause;
{
	Atm_pcb		*atp = (Atm_pcb *)toku;
	struct socket	*so;

	so = atp->atp_socket;

	/*
	 * Save call clearing cause
	 */
	atp->atp_attr.cause.tag = T_ATM_PRESENT;
	atp->atp_attr.cause.v = *cause;

	/*
	 * Set user error code
	 */
	if (so->so_state & SS_ISCONNECTED) {
		so->so_error = ECONNRESET;
		atm_sock_stat.as_connclr[atp->atp_type]++;
	} else {
		so->so_error = ECONNREFUSED;
		atm_sock_stat.as_connfail[atp->atp_type]++;
	}

	/*
	 * Connection is gone
	 */
	atp->atp_conn = NULL;
	soisdisconnected(so);

	/*
	 * Cleanup failed incoming connection setup
	 */
	if (so->so_state & SS_NOFDREF) {
		(void) atm_sock_detach(so);
	}
}

