/* Print Transport (& Session) TPDU */

#include <sys/param.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/ip_var.h>
#include <stdio.h>
#include <ctype.h>
#ifdef __STDC__
#include <stdlib.h>
#endif
#include <unistd.h>

#include "ieee.h"
#include "clnp.h"
#include "interface.h"
#include "addrtoname.h"
#include "tp.h"

/*
 * print ID (really xsel)
 */
static void
tp_id_print(const u_char *cp, int length)
{
    register int i, needhex;
    register u_long id;

    if (length == 0) {
	printf("''");
	return;
    }
    if (length == 1) {
	printf("#%d", *cp);
	return;
    }
    if (length == 2) {
	id = cp[0];
	id <<= 8;
	id |= cp[1];
	if (id < 2048) {
	    printf("#%d", id);
	    return;
	}
    }
    if ((length < 0) || (length > 64)) {
	printf("<%d bytes>", length);
	return;
    }
    for (i = 0, needhex = 0; i < length; i++)
	if (!isprint(cp[i])) {
	    needhex++;
	    break;
	}
    if (!needhex)
	printf("\"%*s\"", length, cp);
    else
	for (i = 0; i < length; i++)
	    printf("%02x", cp[i]);
}

/*
 * print a TPDU
 */
void
tp_print(const u_char *tp, int length, const u_char *cip)
{
    register struct ip *ip = (struct ip *)cip;
    register int len, olen;
    register u_char type, otyp;
    u_char opt, reason;
    u_short srcref, dstref;
    const u_char *data;
    int i, withdata;

    if (vflag)
	printf("\n");
    else
	printf(" ");

    if (ip) {
	printf("%s > %s:",
	       ipaddr_string(&ip->ip_src),
	       ipaddr_string(&ip->ip_dst));
    }

    if (length < 2)
	goto truncated;

    len = *tp++;
    data = tp+len;
    type = *tp++;

    if (tp > snapend)
	return;

    if (len > TP_MAX_HLEN) {
	printf("tp illegal header length %d", len);
	return;
    }

    /* print TPDU type */
    switch (type & TP_TYPE_MASK) {
      case (TP_PI_PDU & TP_TYPE_MASK):
        if ((type & TP_PI_MASK) != TP_PI_PDU)
            goto badtype;
        printf("PI TPDU");
        break;
      case TP_ED_PDU:
	if ((type & TP_ED_MASK) != TP_ED_PDU)
	    goto badtype;
	printf("ED TPDU");
	break;
      case TP_EA_PDU:
	if ((type & TP_EA_MASK) != TP_EA_PDU)
	    goto badtype;
	printf("EA TPDU");
        break;
      case TP_UD_PDU:
	if ((type & TP_UD_MASK) != TP_UD_PDU)
            goto badtype;
        printf("UD TPDU");
        break;
      case TP_RJ_PDU:
        printf("RJ TPDU");
        break;
      case TP_AK_PDU:
        printf("AK TPDU");
        break;
      case TP_ER_PDU:
        if ((type & TP_ER_MASK) != TP_ER_PDU)
            goto badtype;
        printf("ER TPDU");
        break;
      case TP_DR_PDU:
        if ((type & TP_DR_MASK) != TP_DR_PDU)
            goto badtype;
        printf("DR TPDU");
        break;
      case TP_DC_PDU:
        if ((type & TP_DC_MASK) != TP_DC_PDU)
            goto badtype;
        printf("DC TPDU");
        break;
      case TP_CC_PDU:
        printf("CC TPDU");
        break;
      case TP_CR_PDU:
        printf("CR TPDU");
        break;
      case TP_DT_PDU:
        if ((type & TP_DT_MASK) != TP_DT_PDU)
            goto badtype;
        printf("DT TPDU");
        break;
      default:
      badtype:
	printf("not a TPDU, type = %02x", type);
	return;
    }

    length--;
    if (len > length)
	goto truncated;

    length -= len;
    len--;
    withdata = 0;

    /* print fixed part */
    switch (type & TP_TYPE_MASK) {

      /* Protocol Identification */
      case (TP_PI_PDU & TP_TYPE_MASK):
	withdata = 1;
	if (len < 2)
            goto truncated;
        if (tp+2 > snapend)
            return;
        len -= 2;
        if (!vflag) {
            tp += 2;
            break;
        }
	opt = *tp++;
	if (opt == TP_PI_OSI)
	    printf(" proto=OSI TP");
	else
	    printf(" proto=%d", opt);
	if (*tp++ == TP_PI_SHARE)
	    printf(" with sharing");
	break;

      /* Expedited Data */
      case TP_ED_PDU:
	withdata = 1;
	/* falls through */

      /* Expedited Data Acknowledge */
      case TP_EA_PDU:
	/* keep withdata value */
	if (tp+len > snapend)
	    return;
	/* ToDo */
	len = 0;
	break;

      /* Unidata */
      case TP_UD_PDU:
	withdata = 1;
	/* no fixed part */
	break;

      /* Reject */
      case TP_RJ_PDU:
	withdata = 0;
	/* falls through */

      /* Data Acknowledge */
      case TP_AK_PDU:
	withdata = 0;
	if (tp+len > snapend)
	    return;
	/* ToDo */
	len = 0;
	break;

      /* TPDU Error */
      case TP_ER_PDU:
	withdata = 0;
	if (len < 3)
            goto truncated;
        if (tp+3 > snapend)
            return;
        len -= 3;
        if (!vflag) {
            tp += 3;
            break;
        }
        dstref = *tp++;
        dstref <<= 8;
        dstref |= *tp++;
	printf(" (%d<)", dstref);
	reason = *tp++;
	printf(" cause:");
	switch (reason) {
	  case TP_ERR_NSP:
	    printf("not specified");
	    break;
	  case TP_ERR_PARC:
	    printf("invalid parameter code");
	    break;
	  case TP_ERR_TYPE:
	    printf("invalid TPDU type");
	    break;
	  case TP_ERR_PARV:
	    printf("invalid parameter value");
	    break;
	  default:
	    printf("unkown %d", reason);
	    break;
	}
	break;

      /* Disconnect Request */
      case TP_DR_PDU:
	withdata = 1;
	if (len < 5)
	    goto truncated;
	if (tp+5 > snapend)
	    return;
	len -= 5;
	if (!vflag) {
	    tp += 5;
	    break;
	}
	dstref = *tp++;
	dstref <<= 8;
	dstref |= *tp++;
	srcref = *tp++;
	srcref <<= 8;
	srcref |= *tp++;
	printf(" (%d<%d)", dstref, srcref);
	reason = *tp++;
	printf(" reason:");
	switch (reason) {
	  case TP_DRC_NSP:
	    printf("not specified");
	    break;
	  case TP_DRC_TCONG:
	    printf("congestion at TSAP");
	    break;
	  case TP_DRC_SESS:
	    printf("session not attached!");
	    break;
	  case TP_DRC_ADDR:
	    printf("address unknown!");
	    break;
	  case TP_DRC_NDISC:
	    printf("normal disconnect");
	    break;
	  case TP_DRC_CONG:
	    printf("remote congestion");
	    break;
	  case TP_DRC_NEG:
	    printf("negotiation failed!");
	    break;
	  case TP_DRC_DUP:
	    printf("duplicate src-ref");
	    break;
	  case TP_DRC_MIS:
	    printf("mismatched refs");
	    break;
	  case TP_DRC_PROT:
	    printf("protocol error");
	    break;
	  case TP_DRC_ROVF:
	    printf("ref overflow");
	    break;
	  case TP_DRC_NET:
	    printf("network connection refused");
	    break;
	  case TP_DRC_LEN:
	    printf("bad length in heading");
	    break;
	  default:
	    printf("unknown %d", reason);
	    break;
	}
	break;

      /* Disconnect Confirm */
      case TP_DC_PDU:
	withdata = 0;
	if (len < 4)
	    goto truncated;
        if (tp+4 > snapend)
            return;
        len -= 4;
        if (!vflag) {
            tp += 4;
            break;
        }
        dstref = *tp++;
        dstref <<= 8;
        dstref |= *tp++;
        srcref = *tp++;
        srcref <<= 8;
        srcref |= *tp++;
        printf(" (%d<%d)", dstref, srcref);
	break;

      /* Connection Confirm */
      case TP_CC_PDU:
	withdata = 1;
	/* falls through */

      /* Connection Request */
      case TP_CR_PDU:
	withdata = 1;
	if (len < 5)
	    goto truncated;
	if (tp+5 > snapend)
	    return;
	len -= 5;
	if (!vflag) {
	    tp += 5;
	    break;
	}
	dstref = *tp++;
	dstref <<= 8;
	dstref |= *tp++;
	srcref = *tp++;
	srcref <<= 8;
	srcref |= *tp++;
	printf(" (%d<%d)", dstref, srcref);
	opt = *tp++;
	printf(" class=%d", opt >> 4);
	if ((opt & TP_CLO_EXT) == TP_CLO_EXT)
	    printf("x");
	if ((opt & TP_CLO_FLC) & TP_CLO_FLC)
	    printf("f");
	break;

      /* Data */
      case TP_DT_PDU:
	withdata = 1;
	if (tp+len > snapend)
	    return;
	/* ToDo */
	len = 0;
	break;

      default:
	/* can't happen */
	return;
    }

    if (!vflag || (tp+len > snapend)) {
	if (!withdata && (length > 0))
	    /* another TPDU after this one */
	    tp_print(data, length, (u_char *)0);
	else if (type == TP_DT_PDU)
	    /* do session */
	    ses_print(data, length);
	return;
    }

    /* print variable part (options) */
    otyp = 0;
    while (len > 0) {
	otyp = *tp++;
	olen = *tp++;
	switch (otyp) {
	  /* acknowledge time parameter */
	  case TP_PAR_AKT:
	    i = *tp;
	    i <<= 8;
	    i |= *(tp+1);
	    printf(" ack=%d ms", i);
	    break;
	  /* residual error rate parameter */
	  case TP_PAR_RERR:
	    /* ToDo */
	    break;
	  /* priority parameter */
	  case TP_PAR_PRI:
	    i = *tp;
            i <<= 8;
            i |= *(tp+1);
            printf(" pri=%d", i);
	    break;
	  /* transit delay parameter */
	  case TP_PAR_TRDL:
	    /* ToDo */
	    break;
	  /* throughput parameter */
	  case TP_PAR_THR:
	    /* ToDo */
	    break;
	  /* sub-sequence number parameter */
	  case TP_PAR_SUB:
	    i = *tp;
            i <<= 8;
            i |= *(tp+1);
            printf(" subseq=%d", i);
	    break;
	  /* reassignment time parameter */
	  case TP_PAR_TTR:
	    i = *tp;
            i <<= 8;
            i |= *(tp+1);
            printf(" TTR=%d s", i);
	    break;
	  /* flow control confirmation parameter */
	  case TP_PAR_FLOW:
	    /* ToDo */
	    break;
	  /* TPDU max size parameter */
	  case TP_PAR_SIZE:
	    printf(" TPDU size=%d", 1 << (*tp));
	    break;
	  /* invalid TPDU parameter */
	  /* case TP_PAR_INV: CCITT Bozos ! */
	  /* calling TSAP-ID parameter */
	  case TP_PAR_I_ID:
	    if (type == TP_ER_PDU)
		/* really a TP_PAR_INV ! */
		break;
	    printf(" calling TSAP-ID:");
	    tp_id_print(tp, olen);
	    break;
	  /* called TSAP-ID parameter */
	  case TP_PAR_E_ID:
	    printf(" called TSAP-ID:");
            tp_id_print(tp, olen);
            break;
	  /* checksum parameter */
	  case TP_PAR_CKM:
	    break;
	  /* version number parameter */
	  case TP_PAR_VER:
	    printf(" v%d", *tp);
	    break;
	  /* protection parameter */
	  case TP_PAR_PRO:
	    break;
	  /* additional option selection parameter */
	  case TP_PAR_OPT:
	    opt = *tp;
	    printf(" opt=");
	    if (opt & TP_OPT_NED)
		printf("network expedited,");
	    if (opt & TP_OPT_AK)
		printf("receipt conf,");
	    if (opt & TP_OPT_TED)
		printf("transport ED,");
	    /* kludge */
	    if ((opt & TP_OPT_CKM) == 0)
		printf("use checksum");
	    break;
	  /* alternative protocol class parameter */
	  case IP_PAR_ACL:
	    printf(" alternate class=");
	    for (i = 0; i < olen; i++) {
		opt = *(tp+i);
		printf("%d", opt >> 4);
		if (i != (olen-1))
		    printf(",");
	    }
	    break;
	  /* protocol sharing parameter */
	  case TP_PAR_PISH:
	    printf(" sharing with ");
	    for (i = 0; i < olen; i++) {
                opt = *(tp+i);
                printf("%d", opt);
                if (i != (olen-1))
                    printf(",");
            }
	    break;
	  /* additional info parameter */
	  case TP_PAR_ADDI:
	    /* ToDo */
	    break;
	  /* extension parameter */
	  case TP_PAR_EXT:
	    /* ToDo */
	    break;
	  default:
	    printf(" unknown=%02x", otyp);
	    return;
	}
	len -= 2 + olen;
	tp += olen;
	if (len != 0)
	    printf(";");
    }

    if (!withdata && (length > 0))
	/* another TPDU after this one */
	tp_print(data, length, (u_char *)0);
    else if (type == TP_DT_PDU)
	/* do session */
	ses_print(data, length);
    return;

  truncated:
    printf("truncated-tp %d", length);
}

/*
 * print a SPDU
 */
void
ses_print(const u_char *cp, int length)
{
    register u_char type;

    if ((length < 1) || (cp+1 > snapend))
	return;

    type = *cp++;

    /* print SPDU type */
    switch (type) {

      /* Exception Report */
      case SS_ER_PDU:
	printf(" ER SPDU");
	break;

      /* Data Transfert or Give Tokens */
      case SS_DT_PDU:
	printf(" DT/GT SPDU");
	break;

      /* Please Tokens */
      case SS_PT_PDU:
	printf(" PT SPDU");
	break;

      /* Expedited */
      case SS_EX_PDU:
	printf(" EX SPDU");
	break;

      /* Prepare */
      case SS_PR_PDU:
	printf(" PR SPDU");
	break;

      /* Not Finished */
      case SS_NF_PDU:
	printf(" NF SPDU");
        break;

      /* Finish */
      case SS_FN_PDU:
	printf(" FN SPDU");
        break;

      /* Disconnect */
      case SS_DN_PDU:
	printf(" DN SPDU");
        break;

      /* Refuse */
      case SS_RF_PDU:
	printf(" RF SPDU");
        break;

      /* Connect */
      case SS_CN_PDU:
	printf(" CN SPDU");
        break;

      /* Accept */
      case SS_AC_PDU:
	printf(" AC SPDU");
        break;

      /* Connect Data Overflow */
      case SS_CDO_PDU:
	printf(" CDO SPDU");
        break;

      /* Overflow Accept */
      case SS_OA_PDU:
	printf(" OA SPDU");
        break;

      /* Give Tokens Confirm */
      case SS_GTC_PDU:
	printf(" GTC SPDU");
        break;

      /* Give Tokens Ack */
      case SS_GTA_PDU:
	printf(" GTA SPDU");
        break;

      /* Abort or Activity Interrupt */
      case SS_AB_PDU:
	printf(" AB/AI SPDU");
        break;

      /* Abort Accept or Activity Interrupt Ack */
      case SS_AA_PDU:
	printf(" AA/AIA SPDU");
        break;

      /* Activity Resume */
      case SS_AR_PDU:
	printf(" AR SPDU");
        break;

      /* Typed Data */
      case SS_TD_PDU:
	printf(" TD SPDU");
        break;

      /* Resynchronize Ack */
      case SS_RA_PDU:
	printf(" RA SPDU");
        break;

      /* Major Sync Point or Activity End */
      case SS_MAP_PDU:
	printf(" MAP/AE SPDU");
        break;

      /* Major Sync Ack or Activity End Ack */
      case SS_MAA_PDU:
	printf(" MAA/AEA SPDU");
        break;

      /* Activity Start */
      case SS_AS_PDU:
	printf(" AS SPDU");
        break;

      /* Exception Data */
      case SS_ED_PDU:
	printf(" ED SPDU");
        break;

      /* Minor Sync Point */
      case SS_MIP_PDU:
	printf(" MIP SPDU");
        break;

      /* Minor Sync Ack */
      case SS_MIA_PDU:
	printf(" MIA SPDU");
        break;

      /* Resynchronize */
      case SS_RS_PDU:
	printf(" RS SPDU");
        break;

      /* Activity Discard */
      case SS_AD_PDU:
	printf(" AD SPDU");
        break;

      /* Activity Discard Ack */
      case SS_ADA_PDU:
	printf(" ADA SPDU");
        break;

      /* Capability Data */
      case SS_CD_PDU:
	printf(" CD SPDU");
        break;

      /* Capability Data Ack */
      case SS_CDA_PDU:
	printf(" CDA SPDU");
        break;

      default:
	printf(" unknown (%d) SPDU", type);
	return;
    }

    return;
}
