/* Print CLNP packets */

#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 <netinet/udp.h>
#include <netinet/udp_var.h>
#include <netinet/tcp.h>
#include <netinet/tcpip.h>
#include <stdio.h>
#include <string.h>
#ifdef __STDC__
#include <stdlib.h>
#endif
#include <unistd.h>

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

int tuba = 0;
int idrp = 0;

struct clnp c;
struct clnp_fixed cf;
struct clnp_address cad, cas;
struct clnp_segment cs;
struct clnp_option co;
static int segperm, more, errept, type;
static char hex[] = "0123456789abcdef";
u_char src_nsel, dst_nsel;

/*
 * print fixed part of CLNP datagrams.
 */
static void
clnp_fixed_print(void)
{
    if (cf.nlpi != NLPI_CLNP) {
	printf("not a clnp packet ?");
	return;
    }
    if (cf.hlen > CLNP_MAX_HLEN) {
	printf("clnp illegal header length %d", cf.hlen);
	return;
    }
    if (cf.ver != CLNP_VERSION) {
	printf("clnp illegal version %d", cf.ver);
	return;
    }

    /* flags decode */
    segperm = (cf.type & CLNP_SEGPERM) == CLNP_SEGPERM;
    more = (cf.type & CLNP_MORE) == CLNP_MORE;
    errept = (cf.type & CLNP_ERREPT) == CLNP_ERREPT;
    type = cf.type & CLNP_TYPE;

    switch (type) {
      case CLNP_DT_PDU:
	printf("clnp DT");
	break;
      case CLNP_ERR_PDU:
	printf("clnp ERR");
	break;
      case CLNP_ECH_REQ:
	printf("clnp ECHO REQUEST");
	break;
      case CLNP_ECH_REP:
	printf("clnp ECHO REPLY");
	break;
      default:
	printf("clnp unknown PDU %d", type);
	return;
    }

    if (vflag || (cf.ttl <= 1))
	printf(" [ttl %d]", cf.ttl);
}

/*
 * print CLNP datagram addresses
 */
static void
clnp_addresses_print(void) 
{
    register const u_char *a;
    register char *c;
    register int len;
    char src[(NSAP_MAXLEN+1)*2];
    char dst[(NSAP_MAXLEN+1)*2];
    
    src_nsel = dst_nsel = 0;

    if (cas.len > NSAP_MAXLEN) {
	printf("clnp address too long %d", cas.len);
	return;
    }
    for (a = cas.addr, len = cas.len, c = src; len > 0; len--) {
	*c++ = hex[*a >> 4];
	*c++ = hex[*a++ & 0x0f];
    }
    *c = '\0';
    --a;
    src_nsel = *a;

    if (cad.len > NSAP_MAXLEN) {
	printf("clnp address too long %d", cad.len);
	return;
    }
    for (a = cad.addr, len = cad.len, c = dst; len > 0; len--) {
	*c++ = hex[*a >> 4];
	*c++ = hex[*a++ & 0x0f];
    }
    *c = '\0';
    --a;
    dst_nsel = *a;

    printf(" %s > %s", src, dst);
}

/*
 * print segmentation part of a CLNP datagram.
 */
static void
clnp_segment_print(void)
{
    if (segperm)
	printf(" (frag %d:%d/%d@%d%s)", cs.id, cf.slen,
	       cs.tlen, cs.off, more ? "+" : "");
    else
	printf(" (DF)");
}

/*
 * print a CLNP datagram.
 */
void
clnp_print(const u_char *cp, int length)
{
    register const u_char *p;
    register int len;

    (void)bzero((char *)&c, sizeof(struct clnp));
    (void)bzero((char *)&cf, sizeof(struct clnp_fixed));
    (void)bzero((char *)&cs, sizeof(struct clnp_segment));
    c.cf = &cf;
    c.cad = &cad;
    c.cas = &cas;
    c.cs = (struct clnp_segment *)0;
    c.co = (struct clnp_option *)0;
    c.cd = NULL;

    if (length < CLNP_FIXED_SIZE)
	goto truncated;

    p = cp;
    (void)bcopy(p, (char *)&cf, CLNP_FIXED_SIZE);
    PACK(cf.slen, cf.slenh, cf.slenl);
    PACK(cf.chk, cf.chkh, cf.chkl);
    p += CLNP_FIXED_SIZE;

    if ((int)cf.hlen > length)
	goto truncated;

    if (p > snapend) {
	printf("[|clnp]");
	return;
    }

    c.cd = cp + cf.hlen;

    clnp_fixed_print();

    len = cad.len = *p++;
    cad.addr = p;
    p += len;

    if (p > snapend)
	return;

    len = cas.len = *p++;
    cas.addr = p;
    p += len;

    if (p > snapend)
	return;

    clnp_addresses_print();

    if (segperm) {
	(void)bcopy(p, (char *)&cs, CLNP_SEGMENT_PART);
	PACK(cs.id, cs.idh, cs.idl);
	PACK(cs.off, cs.offh, cs.offl);
	PACK(cs.tlen, cs.tlenh, cs.tlenl);
	p += CLNP_SEGMENT_PART;
	c.cs = &cs;

	if (p > snapend)
	    return;
    }
    clnp_segment_print();

    len = cf.hlen - (p - cp);
    if (len < 0) {
	printf("clnp header mismatch");
	return;
    }
    if (!tuba && !idrp && ((len == 0) || !vflag)) {
	if ((type == CLNP_DT_PDU) &&
	    ((c.cs == (struct clnp_segment *)0) || (cs.off == 0)))
	    /* print TPDU ... */
	    tp_print(c.cd, length - cf.hlen, 0);
        return;
    }

    if (vflag)
	printf(" optlen=%d", len);
    if (p+len > snapend)
	return;

    while (len > 0) {
	co.code = *p++;
	co.olen = *p++;
	co.val = p;
	switch (co.code) {
	  case CLNP_OPT_PAD:
	    printf(" PAD %d", co.olen);
	    break;
	  case CLNP_OPT_SECU:
	    printf(" SECU 0x%02x", *p);
	    break;
	  case CLNP_OPT_SR:
	    printf(" SR %d", *p);
	    break;
	  case CLNP_OPT_RR:
	    printf(" RR %d", *p);
	    break;
	  case CLNP_OPT_QOS:
	    printf(" QOS 0x%02x", *p);
	    break;
	  case CLNP_OPT_PRI:
	    printf(" PRI %d", *p);
	    break;
	  case CLNP_OPT_ERR:
	    printf(" ERR 0x%02x %d", p[0], p[1]);
	    break;
	  default:
	    printf(" unknown option %d/%d", co.code, co.olen);
	    return;
	}
	len -= 2 + co.olen;
	p += co.olen;
	/* don't chain option (TODO) */
    }

    if ((type != CLNP_DT_PDU) ||
	((c.cs != (struct clnp_segment *)0) && (cs.off != 0)))
	return;

    length -= cf.hlen;

    /* align packet ! */
    if ((int)c.cd & (sizeof(long)-1)) {
	if ((p = (u_char *)malloc(length)) == 0)
	    return;
	bcopy((char *)c.cd, (char *)p, length);
	snapend = p + length;
	c.cd = p;
    }

    /* test TUBA and IDRP stuff */
    if (tuba && (src_nsel == dst_nsel))
	switch (src_nsel) {
	  case IPPROTO_TCP:
	    tcp_print(c.cd, length, (u_char *)0);
	    return;
	  case IPPROTO_UDP:
	    udp_print(c.cd, length, (u_char *)0);
	    return;
	}
    else if (idrp && (src_nsel == 0) && (dst_nsel == 0))
	if ((length > 0) && (snapend > c.cd))
	    switch (*c.cd) {
	      case NLPI_IDRP:
		idrp_print(c.cd, length);
		return;
	      case NLPI_CLNP:
		printf("\nencapsulated CLNP:");
		clnp_print(c.cd, length);
		return;
	    }
    /* fall through */

    /* print TPDU ... */
    tp_print(c.cd, length, 0);
    return;

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

/*
 * print EON packets
 */
void
eon_print(const u_char *cp, int length, const u_char *cip)
{
    struct eon_hdr *eh;
    struct ip *ip = (struct ip *)cip;

    if (length < 4)
	goto truncated;

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

    eh = (struct eon_hdr *)cp;
    length -= EON_HDR_SIZE;
    cp += EON_HDR_SIZE;

    if (cp > snapend)
	return;

    if (eh->vers != EON_VERSION) {
	printf(" unknown version %d", eh->vers);
	return;
    }
    switch (eh->class) {
      case EON_NORM_ADDR:
	break;
      case EON_MULT_ES:
	printf(" mES");
	break;
      case EON_MULT_IS:
	printf(" mIS");
	break;
      case EON_BROADCAST:
	printf(" broadcast");
	break;
      default:
	printf(" unknown class %d", eh->class);
	return;
    }
    if (vflag)
	printf("\n");
    else
	printf(" ");
    clnp_print(cp, length);
    return;

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