/* Print IS-IS (ISO IS 10589) packets */

#include <sys/param.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#ifdef __STDC__
#include <stdlib.h>
#endif
#include <unistd.h>

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

int IDlength;
u_short PDUlength;
static char hex[] = "0123456789abcdef";

/*
 * print IS-IS header.
 */
static void
isis_header_print(const struct isis_header *hdr, int length)
{
    if (hdr->nlpi != NLPI_ISIS) {
	printf("not an isis packet ?");
	return;
    }
    if (hdr->hlen <= 8) {
	printf("isis illegal header length %d", hdr->hlen);
	return;
    }
    if (hdr->verx != ISIS_VERX) {
	printf("isis illegal version/pidx %d", hdr->verx);
	return;
    }
    if (hdr->vers != ISIS_VERSION) {
	printf("isis illegal version %d", hdr->vers);
	return;
    }

    switch (hdr->idlen) {
      case ISIS_ID_DEF:
	IDlength = 6;
	break;
      case ISIS_ID_1:
	IDlength = 1;
	break;
      case ISIS_ID_2:
	IDlength = 2;
	break;
      case ISIS_ID_3:
	IDlength = 3;
	break;
      case ISIS_ID_4:
	IDlength = 4;
	break;
      case ISIS_ID_5:
	IDlength = 5;
	break;
      case ISIS_ID_6:
	IDlength = 6;
	break;
      case ISIS_ID_7:
	IDlength = 7;
	break;
      case ISIS_ID_8:
	IDlength = 8;
	break;
      case ISIS_ID_NUL:
	IDlength = 0;
	break;
      default:
	if (vflag)
	    printf("isis illegal ID length %d", hdr->idlen);
	IDlength = 6;
	break;
    }
    if (vflag && (IDlength != 6))
	printf("IDlength=%d", IDlength);

    switch (hdr->type & 0x1f) {
      case ISIS_L1_LAN_IIH:
	printf("isis L1 LAN IIH");
	break;
      case ISIS_L2_LAN_IIH:
	printf("isis L2 LAN IIH");
        break;
      case ISIS_P2P_IIH:
	printf("isis P2P IIH");
	break;
      case ISIS_L1_LSP:
	printf("isis L1 LSP");
	break;
      case ISIS_L2_LSP:
	printf("isis L2 LSP");
        break;
      case ISIS_L1_CSNP:
	printf("isis L1 CNSP");
	break;
      case ISIS_L2_CSNP:
	printf("isis L2 CNSP");
        break;
      case ISIS_L1_PSNP:
	printf("isis L1 PSNP");
	break;
      case ISIS_L2_PSNP:
	printf("isis L2 PSNP");
        break;
      default:
	printf("isis unknown PDU %d", hdr->type & 0x1f);
	return;
    }
}

/*
 * print circuit type
 */
static void
isis_CT_print(u_char ct)
{
    switch (ct & 0x3) {
      case ISIS_CIR_RES:
	printf("isis illegal circuit type %d", ct);
	return;
      case ISIS_CIR_L1:
	printf("L1");
	break;
      case ISIS_CIR_L2:
	printf("L2");
        break;
      case ISIS_CIR_L12:
	printf("L1&L2");
        break;
    }
}

/*
 * print address
 */
static void
isis_addr_print(const u_char *cp, u_char len)
{
    register const u_char *p;

    if (len > NSAP_MAXLEN) {
	printf("isis address too long %d", len);
	return;
    }
    for (p = cp; len > 0; len--) {
	putchar(hex[*p >> 4]);
	putchar(hex[*p++ & 0x0f]);
    }
}

/*
 * print prefix
 */
static void
isis_prefix_print(const u_char *cp, u_char len)
{
    register const u_char *p;

    if ((int)len > NSAP_MAXLEN+NSAP_MAXLEN) {
	printf("isis prefix too long %d", len);
	return;
    }
    for (p = cp; len > 0; len -= 2) {
	putchar(hex[*p >> 4]);
	if (len > 1)
	    putchar(hex[*p++ & 0x0f]);
    }
}

/*
 * print ID
 */
static void
isis_ID_print(const u_char *cp)
{
    printf("%02x:%02x:%02x:%02x:%02x:%02x",
	   cp[0], cp[1], cp[2], cp[3], cp[4], cp[5]);
}

/*
 * print ID with pseudo-node
 */
static void
isis_ID1_print(const u_char *cp)
{
    printf("%02x:%02x:%02x:%02x:%02x:%02x.%02x",
	   cp[0], cp[1], cp[2], cp[3], cp[4], cp[5], cp[6]);
}

/*
 * print ID with pseudo-node and LSP number
 */
static void
isis_ID2_print(const u_char *cp)
{
    printf("%02x:%02x:%02x:%02x:%02x:%02x.%02x-%02x",
	   cp[0], cp[1], cp[2], cp[3], cp[4], cp[5], cp[6], cp[7]);
}

/*
 * print LSP entries
 */
static void
isis_lspent_print(const struct lsp_entry *lep)
{
    register u_short s, l;

    PACK(s, lep->life[0], lep->life[1]);
    printf(" life=%d ID=", s);
    isis_ID2_print(lep->lspid);
    l = lep->lspseq[0] << 24;
    l |= lep->lspseq[1] << 16;
    l |= lep->lspseq[2] << 8;
    l |= lep->lspseq[3];
    printf(" seq=%u", l);
}

/*
 * print IS-IS header details.
 */
static void
isis_fheader_print(const struct isis_header *hdr)
{
    register u_short s;
    register u_long l;

    printf("\n ");
    PDUlength = 0;
    switch (hdr->type & 0x1f) {
      case ISIS_L1_LAN_IIH:
      case ISIS_L2_LAN_IIH:
	isis_CT_print(hdr->iih.cirt);
	printf(" srcID=");
	isis_ID_print(hdr->iih.srcid);
	PACK(s, hdr->iih.hold[0], hdr->iih.hold[1]);
	printf(" hold=%d", s);
	PACK(PDUlength, hdr->iih.len[0], hdr->iih.len[1]);
	printf(" pri=%d LanIS=", hdr->iih.lan_iih.pri);
	isis_ID1_print(hdr->iih.lan_iih.lanid);
	break;
      case ISIS_P2P_IIH:
	isis_CT_print(hdr->iih.cirt);
        printf(" srcID=");
        isis_ID_print(hdr->iih.srcid);
        PACK(s, hdr->iih.hold[0], hdr->iih.hold[1]);
        printf(" hold=%d", s);
	PACK(PDUlength, hdr->iih.len[0], hdr->iih.len[1]);
	printf(" LcID=%d", hdr->iih.p2p_iih.locid);
	break;
      case ISIS_L1_LSP:
      case ISIS_L2_LSP:
	PACK(PDUlength, hdr->lsp.len[0], hdr->lsp.len[1]);
	PACK(s, hdr->lsp.life[0], hdr->lsp.life[1]);
	printf("life=%d LspID=", s);
	isis_ID2_print(hdr->lsp.lspid);
	l = hdr->lsp.seq[0] << 24;
	l |= hdr->lsp.seq[1] << 16;
	l |= hdr->lsp.seq[2] << 8;
	l |= hdr->lsp.seq[3];
	printf(" seq=%u", l);
	printf(" flags=%02x",  hdr->lsp.flags);
	break;
      case ISIS_L1_CSNP:
      case ISIS_L2_CSNP:
	PACK(PDUlength, hdr->csnp.len[0], hdr->csnp.len[1]);
	printf("srcID=");
	isis_ID_print(hdr->csnp.srcid);
	printf(" <<");
	isis_ID2_print(hdr->csnp.slspid);
	printf(" -- ");
	isis_ID2_print(hdr->csnp.elspid);
	printf(">>");
	break;
      case ISIS_L1_PSNP:
      case ISIS_L2_PSNP:
	PACK(PDUlength, hdr->psnp.len[0], hdr->psnp.len[1]);
	printf("srcID=");
	isis_ID_print(hdr->psnp.srcid);
	break;
    }
}

/*
 * print IS-IS packets.
 */
void
isis_print(const u_char *cp, int length)
{
    register const struct isis_header *hdr;
    register int n;
    register u_char code, len;
    struct in_addr addr;

    hdr = (const struct isis_header *)cp;

    if (length < 8)
	goto truncated;

    if (cp+8 > snapend) {
	printf("[|isis]");
	return;
    }

    isis_header_print(hdr, length);

    if ((int)hdr->hlen > length)
	return;

    cp += hdr->hlen;

    if (cp > snapend)
	return;

    if (!vflag || (IDlength != 6))
	return;

    isis_fheader_print(hdr);

    if (PDUlength == 0)
	return;

    PDUlength -= hdr->hlen;

    if (cp+PDUlength > snapend)
	return;

    while (PDUlength > 0) {
	code = *cp++;
	len = *cp++;
	PDUlength -= len + 2;
	switch (code) {
	  case ISIS_AREA_ADDR:
	    printf("\n Area=");
	    while (len > 0) {
		code = *cp++;
		len--;
		isis_addr_print(cp, code);
		cp += code;
		len -= code;
		if (len > 0)
		    printf(", ");
	    }
	    break;
	  case ISIS_IS_NEIGHB:
	    code = *cp++;
	    len--;
	    while (len > 0) {
		printf("\n IS neighbor=%s%d/%d/%d/%d@",
		       (code ? "V" : ""), *cp++, *cp++, *cp++, *cp++);
		isis_ID1_print(cp);
		cp += IDlength+1;
		len -= IDlength+5;
		code = 0;
	    }
	    break;
	  case ISIS_ES_NEIGHB:
	    printf("\n ES neighbors=%d/%d/%d/%d@", *cp++, *cp++, *cp++, *cp++);
	    len -= 4;
	    while (len > 0) {
		isis_ID_print(cp);
		cp += IDlength;
		len -= IDlength;
		if (len > 0)
		    printf(", ");
	    }
	    break;
	  case ISIS_PD_L2_IS:
	    printf("\n PD L2 IS=");
	    isis_ID_print(cp);
	    cp += IDlength;
	    len -= IDlength;
	    break;
	  case ISIS_PRE_NEIGHB:
	    printf("\n Prefix neighbor=%d/%d/%d/%d@", 
		   *cp++, *cp++, *cp++, *cp++);
	    len -= 4;
	    while (len > 0) {
		code = *cp++;
		len--;
		isis_prefix_print(cp, code);
		code = ((u_int)code+1) >> 1;
		cp += code;
		len -= code;
		if (len > 0)
		    printf(", ");
            }
	    break;
	  case ISIS_ANEIGHBOUR:
	    if (len == 0)
		break;
	    printf("\n IS neighbors=");
	    while (len > 0) {
		isis_ID_print(cp);
		cp += IDlength;
		len -= IDlength;
		if (len > 0)
		    printf(", ");
	    }
	    break;
	  case ISIS_PADDING:
	    cp += len;
	    break;
	  case ISIS_LSP_ENT:
	    while (len > 0) {
		printf("\n LSP:");
		isis_lspent_print((const struct lsp_entry *)cp);
		cp += 10+IDlength;
		len -= 10+IDlength;
	    }
	    break;
	  case ISIS_AUTH_INFO:
	    cp += len;
	    break;
	  case ISIS_IP_INT_R:
	    printf("\n IP int=");
	    while (len > 0) {
		(void)bcopy((char *)(cp+4), (char *)&addr, sizeof(addr));
		printf("%s", inet_ntoa(addr));
		(void)bcopy((char *)(cp+8), (char *)&addr, sizeof(addr));
		printf("[%s]", inet_ntoa(addr));
		printf(":%d/%d/%d/%d", cp[0], cp[1], cp[2], cp[3]);
		cp += 12;
		len -= 12;
		if (len > 0)
		    printf("\n");
            }
	    break;
	  case ISIS_PROTO:
	    printf("\n Proto=");
	    while (len > 0) {
		code = *cp++;
		len--;
		switch (code) {
		  case NLPI_CLNP:
		    printf("CLNP");
		    break;
		  case NLPI_DODIP:
		    printf("IP");
		    break;
		  default:
		    printf("%02x", code);
		    break;
		}
		if (len > 0)
		    printf(", ");
	    }
	    break;
	  case ISIS_IP_EXT_R:
	    printf("\n IP ext=");
	    while (len > 0) {
		(void)bcopy((char *)(cp+4), (char *)&addr, sizeof(addr));
		printf("%s", inet_ntoa(addr));
		(void)bcopy((char *)(cp+8), (char *)&addr, sizeof(addr));
		printf("[%s]", inet_ntoa(addr));
		printf(":%d/%d/%d/%d", cp[0], cp[1], cp[2], cp[3]);
		cp += 12;
		len -= 12;
		if (len > 0)
		    printf("\n");
            }
	    break;
	  case ISIS_IP_IDRP:
	    code = *cp++;
	    len--;
	    printf("\n IDRP=");
	    switch (code) {
	      case ISIS_IDRP_RES0:
		printf("reserved(0)");
		break;
	      case ISIS_IDRP_LOCAL:
		printf("local");
		break;
	      case ISIS_IDRP_ASN:
		PACK(n,*cp++,*cp++);
		printf("AS# %d", n);
		break;
	      default:
		printf("type=%d", code);
		break;
	    }
	    break;
	  case ISIS_IPINTA:
	    printf("\n IP int@=");
	    while (len > 0) {
		(void)bcopy((char *)cp, (char *)&addr, sizeof(addr));
		printf("%s", inet_ntoa(addr));
		cp += 4;
		len -= 4;
		if (len > 0)
		    printf(", ");
	    }
	    break;
	  case ISIS_IP_AUTH:
	    cp += len;
	    break;
	  case ISIS_ROUTER_ID:
	    (void)bcopy((char *)cp, (char *)&addr, sizeof(addr));
	    printf("\n Router-ID=%s", inet_ntoa(addr));
	    cp += 4;
	    break;
	  default:
	    printf("\n unknown field %d", code);
	    cp += len;
	    break;
	}
    }

    return;

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

}
