/* sap.c - SAP manipulations */
 
/* Written 1996,1997 by Werner Almesberger, EPFL-LRC */
 

#include <stdlib.h>
#include <string.h>
#include <linux/atm.h>
#include <linux/atmdev.h>
#include <linux/atmsvc.h>

#include "atm.h"
#include "atmd.h"
#include "uni.h"
#include "qlib.h"
#include <q.out.h>

#include "common.h"
#include "sap.h"


#define COMPONENT "SAP"


static int class_compat(const struct atm_trafprm *tx,
  const struct atm_trafprm *rx)
{
    if (tx->traffic_class == ATM_NONE || tx->traffic_class == ATM_ANYCLASS)
	return 1;
    if (rx->traffic_class == ATM_UBR || rx->traffic_class == ATM_ANYCLASS)
	return 1;
	/* don't apply CAC to PCR */
    if (tx->traffic_class != rx->traffic_class) return 0;
	/* ignore special cases like CBR to VBR for now */
    switch (tx->traffic_class) {
	case ATM_CBR:
	    if (!rx->max_pcr || rx->max_pcr == ATM_MAX_PCR) return 1;
	    return tx->min_pcr <= rx->max_pcr;
	    /* Actually, we shouldn't look at min_pcr, because there's no
	       bandwidth negotiation anyway. */
	default:
	    diag(COMPONENT,DIAG_ERROR,"unsupported traffic class %d\n",
	      tx->traffic_class);
	    return 0;
    }
}


static int bhli_compat(const struct atm_bhli *old,const struct atm_bhli *new,
  struct atm_bhli *res)
{
    int length;

    if (res) *res = *new;
/*   if (!old->hl_type) return 1; --- GONE */
    if (old->hl_type != new->hl_type) return 0;
    switch (old->hl_type) {
	case ATM_HL_ISO:
	case ATM_HL_USER:
	    length = old->hl_length;
	    if (length != new->hl_length) return 0;
	    break;
#ifdef UNI30
	case ATM_HL_HLP:
	    length = 4;
	    break;
#endif
	case ATM_HL_VENDOR:
	    length = 7;
	    break;
	default:
	    length = 0;
    }
    return !length || !memcmp(old->hl_info,new->hl_info,length);
}


static int match_blli(const struct atm_blli *a,const struct atm_blli *b)
{
    if (a->l2_proto != b->l2_proto || a->l3_proto != b->l3_proto) return 0;
    switch (a->l3_proto) {
	case ATM_L3_TR9577:
	    if (a->l3.tr9577.ipi != b->l3.tr9577.ipi) return 0;
	    if (a->l3.tr9577.ipi == NLPID_IEEE802_1_SNAP)
		if (memcmp(a->l3.tr9577.snap,b->l3.tr9577.snap,5)) return 0;
	    break;
        case ATM_L3_USER:
	    if (a->l3.user != b->l3.user) return 0;
	    break;
	default:
    }
    return 1;
}


static int blli_compat(const struct atm_blli *old,const struct atm_blli *new,
   struct atm_blli *res)
{
    int i,j;

    if (!blli_in_use(*old)) return 1;
    for (i = 0; i < ATM_MAX_BLLI && blli_in_use(old[i]); i++)
	for (j = 0; j < ATM_MAX_BLLI && blli_in_use(new[j]); j++)
	    if (match_blli(old+i,new+j)) {
		if (res) *res = new[j];
		return 1;
	    }
    return 0;
}


int sap_compat(const struct sockaddr_atmsvc *old_addr,
  const struct sockaddr_atmsvc *new_addr,struct sockaddr_atmsvc *res_addr,
  const struct atm_sap *old_sap,const struct atm_sap *new_sap,
  struct atm_sap *res_sap,const struct atm_qos *old_qos,
  const struct atm_qos *new_qos,struct atm_qos *res_qos)
{
    if (atmsvc_addr_in_use(*old_addr) && !atm_equal(old_addr,new_addr,0,0))
	return 0;
    if (res_qos) *res_qos = *new_qos;
    if (old_qos->txtp.max_sdu && new_qos->rxtp.max_sdu &&
      old_qos->txtp.max_sdu > new_qos->rxtp.max_sdu) return 0;
    if (new_qos->txtp.max_sdu && old_qos->rxtp.max_sdu &&
      new_qos->txtp.max_sdu > old_qos->rxtp.max_sdu) return 0;
    if (!class_compat(&old_qos->txtp,&new_qos->rxtp) ||
      !class_compat(&new_qos->txtp,&old_qos->rxtp)) return 0;
    if (res_sap) *res_sap = *new_sap;
    if (!bhli_compat(&old_sap->bhli,&new_sap->bhli,res_sap ? &res_sap->bhli :
      NULL)) return 0;
    if (!blli_compat(old_sap->blli,new_sap->blli,res_sap ? res_sap->blli :
      NULL)) return 0;
    if (res_sap) memset(res_sap->blli+1,0,sizeof(struct atm_blli)*2);
    return 1;
}


static int encode_blli(Q_DSC *dsc,const struct atm_blli *blli)
{
    if (blli->l2_proto != ATM_L2_NONE) {
	q_assign(dsc,QF_uil2_proto,blli->l2_proto);
	switch (blli->l2_proto) {
	    case ATM_L2_X25_LL:
	    case ATM_L2_X25_ML:
	    case ATM_L2_HDLC_ARM:
	    case ATM_L2_HDLC_NRM:
	    case ATM_L2_HDLC_ABM:
	    case ATM_L2_Q922:
	    case ATM_L2_ISO7776:
		if (blli->l2.itu.mode != ATM_IMD_NONE)
		    q_assign(dsc,QF_l2_mode,blli->l2.itu.mode);
		if (blli->l2.itu.window)
		    q_assign(dsc,QF_window_size,blli->l2.itu.window);
		break;
	    case ATM_L2_USER:
		q_assign(dsc,QF_user_l2,blli->l2.user);
		break;
	    default:
		break;
	}
    }
    if (blli->l3_proto != ATM_L3_NONE) {
	q_assign(dsc,QF_uil3_proto,blli->l3_proto);
	switch (blli->l3_proto) {
	    case ATM_L3_X25:
	    case ATM_L3_ISO8208:
	    case ATM_L3_X223:
		if (blli->l3.itu.mode != ATM_IMD_NONE)
		    q_assign(dsc,QF_l3_mode,blli->l3.itu.mode);
		if (blli->l3.itu.def_size)
		    q_assign(dsc,QF_def_pck_size,blli->l3.itu.def_size);
		if (blli->l3.itu.window)
		    q_assign(dsc,QF_pck_win_size,blli->l3.itu.window);
		break;
	    case ATM_L3_TR9577:
		q_assign(dsc,QF_ipi_high,blli->l3.tr9577.ipi >> 1);
		q_assign(dsc,QF_ipi_low,blli->l3.tr9577.ipi & 1);
		if (blli->l3.tr9577.ipi == NLPID_IEEE802_1_SNAP) {
		    q_write(dsc,QF_oui,blli->l3.tr9577.snap,3);
		    q_write(dsc,QF_pid,blli->l3.tr9577.snap+3,2);
		}
		break;
	    case ATM_L3_USER:
		q_assign(dsc,QF_user_l3,blli->l3.user);
		break;
	    default:
		diag(COMPONENT,DIAG_ERROR,"bad l3_proto (%d)",
		  blli->l3_proto);
		return -EINVAL;
	}
    }
    return 0;
}


int sap_encode(Q_DSC *dsc,const struct sockaddr_atmsvc *addr,
  const struct atm_sap *sap,const struct atm_qos *qos)
{
    int error,pcr;

    if (*addr->sas_addr.pub)
	q_write(dsc,QF_cdpn_e164,(void *) addr->sas_addr.pub,
	  strlen(addr->sas_addr.pub));
    else if (*addr->sas_addr.prv)
	    q_write(dsc,QF_cdpn_esa,(void *) addr->sas_addr.prv,ATM_ESA_LEN);
	else return -EDESTADDRREQ;
    if (qos->txtp.traffic_class == ATM_UBR || qos->rxtp.traffic_class ==
      ATM_UBR) q_assign(dsc,QF_best_effort,0);
#ifdef UNI40
    if (qos->txtp.traffic_class == ATM_CBR || qos->rxtp.traffic_class ==
      ATM_CBR) q_assign(dsc,QF_trans_cap,ATM_TC_CBR);
#endif
    switch (qos->txtp.traffic_class) {
	case ATM_NONE:
	    q_assign(dsc,QF_fw_pcr_01,0);
	    break;
	case ATM_UBR:
	    /* fall through */
	case ATM_CBR:
	    /* here's a bit of policy: send the highest value we have */
	    pcr = SELECT_TOP_PCR(qos->txtp);
	    diag(COMPONENT,DIAG_DEBUG,"fwd %d (%d..%d)",pcr,
	      qos->txtp.min_pcr,qos->txtp.max_pcr);
	    if (pcr == ATM_MAX_PCR) pcr = ATM_OC3_PCR;
	    q_assign(dsc,QF_fw_pcr_01,pcr);
	    break;
        default:
	    diag(COMPONENT,DIAG_ERROR,"bad TX class (%d)",
	      qos->txtp.traffic_class);
	    return -EINVAL;
    }
    switch (qos->rxtp.traffic_class) {
	case ATM_NONE:
	    q_assign(dsc,QF_bw_pcr_01,0);
	    break;
	case ATM_UBR:
	    /* fall through */
	case ATM_CBR:
	    pcr = SELECT_TOP_PCR(qos->rxtp);
	    diag(COMPONENT,DIAG_DEBUG,"bwd %d (%d..%d)",pcr,
	      qos->rxtp.min_pcr,qos->rxtp.max_pcr);
	    if (pcr == ATM_MAX_PCR) pcr = ATM_OC3_PCR;
	    q_assign(dsc,QF_bw_pcr_01,pcr);
	    break;
        default:
	    diag(COMPONENT,DIAG_ERROR,"bad RX class (%d)",
	      qos->rxtp.traffic_class);
	    return -EINVAL;
    }
    if (qos->txtp.max_sdu) q_assign(dsc,QF_fw_max_sdu,qos->txtp.max_sdu);
    if (qos->rxtp.max_sdu) q_assign(dsc,QF_bw_max_sdu,qos->rxtp.max_sdu);
    /* @@@ bearer class ? */
    /* @@@ QOS class ? */
    if (sap->bhli.hl_type != ATM_HL_NONE) {
        q_assign(dsc,QF_hli_type,sap->bhli.hl_type-1);
        switch (sap->bhli.hl_type) {
            case ATM_HL_ISO:
                q_write(dsc,QF_iso_hli,sap->bhli.hl_info,sap->bhli.hl_length);
                break;
            case ATM_HL_USER:
                q_write(dsc,QF_user_hli,sap->bhli.hl_info,sap->bhli.hl_length);
                break;
#ifdef UNI30
            case ATM_HL_HLP:
                q_write(dsc,QF_hlp,sap->bhli.hl_info,4);
                break;
#endif
            case ATM_HL_VENDOR:
		q_write(dsc,QF_hli_oui,sap->bhli.hl_info,3);
		q_write(dsc,QF_app_id, sap->bhli.hl_info+3,4);
                break;
            default:
                diag(COMPONENT,DIAG_ERROR,"bad hl_type (%d)",
		  sap->bhli.hl_type);
		return -EINVAL;
        }
    }
    if (!blli_in_use(sap->blli[0])) return 0;
    q_instance(dsc,QG_blli1);
    error = encode_blli(dsc,sap->blli);
    if (error) return 0;
    if (!blli_in_use(sap->blli[1])) return 0;
    q_instance(dsc,QG_blli2);
    error = encode_blli(dsc,sap->blli+1);
    if (error) return 0;
    if (!blli_in_use(sap->blli[2])) return 1;
    q_instance(dsc,QG_blli3);
    return encode_blli(dsc,sap->blli+2);
}


static void decode_blli(Q_DSC *dsc,struct atm_blli *blli)
{
#define GET(var,field) \
   ({ if (q_present(dsc,field)) blli->var = q_fetch(dsc,field); })

    if (q_present(dsc,QF_uil2_proto)) {
	blli->l2_proto = q_fetch(dsc,QF_uil2_proto);
	GET(l2.itu.mode,QF_l2_mode);
	GET(l2.itu.window,QF_window_size);
	GET(l2.user,QF_user_l2);
    }
    if (q_present(dsc,QF_uil3_proto)) {
	blli->l3_proto = q_fetch(dsc,QF_uil3_proto);
	GET(l3.itu.mode,QF_l3_mode);
	GET(l3.itu.def_size,QF_def_pck_size);
	GET(l3.itu.window,QF_pck_win_size);
	GET(l3.user,QF_user_l3);
	if (q_present(dsc,QF_ipi_high)) {
	    blli->l3.tr9577.ipi = q_fetch(dsc,QF_ipi_high) << 1;
	    if (blli->l3.tr9577.ipi != NLPID_IEEE802_1_SNAP)
		blli->l3.tr9577.ipi |= q_fetch(dsc,QF_ipi_low);
	    else if (!q_present(dsc,QF_oui)) blli->l3.tr9577.ipi |= 1;
		else {
		    q_read(dsc,QF_oui,blli->l3.tr9577.snap,3);
		    q_read(dsc,QF_pid,blli->l3.tr9577.snap+3,2);
		}
	}
    }
#undef GET
}


void sap_decode(Q_DSC *dsc,struct sockaddr_atmsvc *addr,struct atm_sap *sap,
  struct atm_qos *qos)
{
    memset(addr,0,sizeof(*addr));
    memset(sap,0,sizeof(*sap));
    memset(qos,0,sizeof(*qos));
    addr->sas_family = AF_ATMSVC;
    if (q_present(dsc,QF_cdpn_e164))
	(void) q_read(dsc,QF_cdpn_e164,(void *) &addr->sas_addr.pub,
	  ATM_E164_LEN);
    else if (q_present(dsc,QF_cdpn_esa))
	    (void) q_read(dsc,QF_cdpn_esa,(void *) &addr->sas_addr.prv,
	      ATM_ESA_LEN);
    if (q_present(dsc,QF_aal_type))
	if (q_fetch(dsc,QF_aal_type) != 5)
	    diag(COMPONENT,DIAG_ERROR,"AAL type %d requested",
	      q_fetch(dsc,QF_aal_type));
    if (q_present(dsc,QF_best_effort)) {
	qos->txtp.traffic_class = qos->rxtp.traffic_class = ATM_UBR;
	diag(COMPONENT,DIAG_DEBUG,"UBR");
    }
    else {
	qos->txtp.traffic_class = qos->rxtp.traffic_class = ATM_CBR;
	diag(COMPONENT,DIAG_DEBUG,"CBR");
    }
    qos->txtp.max_pcr = qos->rxtp.max_pcr = 0;
    /* unbalanced decoding - always sets upper bound */
    if (q_present(dsc,QF_fw_pcr_01)) {
	qos->rxtp.min_pcr = 0;
	qos->rxtp.max_pcr = q_fetch(dsc,QF_fw_pcr_01);
    }
    if (q_present(dsc,QF_bw_pcr_01)) {
	qos->txtp.min_pcr = 0;
	qos->txtp.max_pcr = q_fetch(dsc,QF_bw_pcr_01);
    }
    if (!qos->txtp.max_pcr) qos->txtp.traffic_class = ATM_NONE;
    if (!qos->rxtp.max_pcr) qos->rxtp.traffic_class = ATM_NONE;
    diag(COMPONENT,DIAG_DEBUG,"fwd %d..%d bwd %d..%d",
      qos->rxtp.min_pcr,qos->rxtp.max_pcr,qos->txtp.min_pcr,
      qos->txtp.max_pcr);
    /* SHOULD ... fail call if anything is missing ... @@@ */
    if (q_present(dsc,QF_bw_max_sdu))
	qos->txtp.max_sdu = q_fetch(dsc,QF_bw_max_sdu);
    if (q_present(dsc,QF_fw_max_sdu))
	qos->rxtp.max_sdu = q_fetch(dsc,QF_fw_max_sdu);
    if (q_present(dsc,QG_bhli)) {
	sap->bhli.hl_type = q_fetch(dsc,QF_hli_type)+1;
	switch (sap->bhli.hl_type) {
	    case ATM_HL_ISO:
		sap->bhli.hl_length = q_length(dsc,QF_iso_hli);
		q_read(dsc,QF_iso_hli,sap->bhli.hl_info,sap->bhli.hl_length);
		break;
	    case ATM_HL_USER:
		sap->bhli.hl_length = q_length(dsc,QF_user_hli);
		q_read(dsc,QF_user_hli,sap->bhli.hl_info,sap->bhli.hl_length);
		break;
#ifdef UNI30
	    case ATM_HL_HLP:
		sap->bhli.hl_length = 4;
		q_read(dsc,QF_hlp,sap->bhli.hl_info,4);
		break;
#endif
	    case ATM_HL_VENDOR:
		sap->bhli.hl_length = 7;
		q_read(dsc,QF_hli_oui,sap->bhli.hl_info,3);
		q_read(dsc,QF_app_id,sap->bhli.hl_info+3,4);
		break;
	    default:
		diag(COMPONENT,DIAG_FATAL,"unrecognized hl_type");
	}
    }
    if (!q_present(dsc,QG_blli1)) return;
    q_instance(dsc,QG_blli1);
    decode_blli(dsc,sap->blli);
    if (!q_present(dsc,QG_blli2)) return;
    q_instance(dsc,QG_blli2);
    decode_blli(dsc,sap->blli+1);
    if (!q_present(dsc,QG_blli3)) return;
    q_instance(dsc,QG_blli3);
    decode_blli(dsc,sap->blli+2);
}
