#include <u.h>
#include <libc.h>
#include <ip.h>
#include <libsec.h>
#include "dat.h"
#include "protos.h"


/*
*  OSPF packets
*/
typedef struct Ospfpkt  Ospfpkt;
struct Ospfpkt
{
       uchar   version;
       uchar   type;
       uchar   length[2];
       uchar   router[4];
       uchar   area[4];
       uchar   sum[2];
       uchar   autype[2];
       uchar   auth[8];
       uchar   data[1];
};
#define OSPF_HDRSIZE    24

enum
{
       OSPFhello=      1,
       OSPFdd=         2,
       OSPFlsrequest=  3,
       OSPFlsupdate=   4,
       OSPFlsack=      5,
};


char *ospftype[] = {
       [OSPFhello]     "hello",
       [OSPFdd]        "data definition",
       [OSPFlsrequest] "link state request",
       [OSPFlsupdate]  "link state update",
       [OSPFlsack]     "link state ack",
};

char*
ospfpkttype(int x)
{
       static char type[16];

       if(x > 0 && x <= OSPFlsack)
               return ospftype[x];
       sprint(type, "type %d", x);
       return type;
}

char*
ospfauth(Ospfpkt *ospf)
{
       static char auth[100];

       switch(ospf->type){
       case 0:
               return "no authentication";
       case 1:
               sprint(auth, "password(%8.8ux %8.8ux)", NetL(ospf->auth),
                       NetL(ospf->auth+4));
               break;
       case 2:
               sprint(auth, "crypto(plen %d id %d dlen %d)", NetS(ospf->auth),
                       ospf->auth[2], ospf->auth[3]);
               break;
       default:
               sprint(auth, "auth%d(%8.8ux %8.8ux)", NetS(ospf->autype), NetL(ospf->auth),
                       NetL(ospf->auth+4));
       }
       return auth;
}

typedef struct Ospfhello        Ospfhello;
struct Ospfhello
{
       uchar   mask[4];
       uchar   interval[2];
       uchar   options;
       uchar   pri;
       uchar   deadint[4];
       uchar   designated[4];
       uchar   bdesignated[4];
       uchar   neighbor[1];
};

char*
seprintospfhello(char *p, char *e, void *a)
{
       Ospfhello *h = a;

       return seprint(p, e, "%s(mask %V interval %d opt %ux pri %ux deadt %d designated %V bdesignated %V)",
               ospftype[OSPFhello],
               h->mask, NetS(h->interval), h->options, h->pri,
               NetL(h->deadint), h->designated, h->bdesignated);
}

enum
{
       LSARouter=      1,
       LSANetwork=     2,
       LSASummN=       3,
       LSASummR=       4,
       LSAASext=       5
};


char *lsatype[] = {
       [LSARouter]     "Router LSA",
       [LSANetwork]    "Network LSA",
       [LSASummN]      "Summary LSA (Network)",
       [LSASummR]      "Summary LSA (Router)",
       [LSAASext]      "LSA AS external",
};

char*
lsapkttype(int x)
{
       static char type[16];

       if(x > 0 && x <= LSAASext)
               return lsatype[x];
       sprint(type, "type %d", x);
       return type;
}

/* OSPF Link State Advertisement Header */
/* rfc2178 section 12.1 */
/* data of Ospfpkt point to a 4-uchar value that is the # of LSAs */
struct OspfLSAhdr {
       uchar   lsage[2];
       uchar   options;        /* 0x2=stub area, 0x1=TOS routing capable */

       uchar   lstype; /* 1=Router-LSAs
                                                * 2=Network-LSAs
                                                * 3=Summary-LSAs (to network)
                                                * 4=Summary-LSAs (to AS boundary routers)
                                                * 5=AS-External-LSAs
                                                */
       uchar   lsid[4];
       uchar   advtrt[4];

       uchar   lsseqno[4];
       uchar   lscksum[2];
       uchar   lsalen[2];      /* includes the 20 byte lsa header */
};

struct Ospfrt {
       uchar   linkid[4];
       uchar   linkdata[4];
       uchar   typ;
       uchar   numtos;
       uchar   metric[2];

};

struct OspfrtLSA {
       struct OspfLSAhdr       hdr;
       uchar                   netmask[4];
};

struct OspfntLSA {
       struct OspfLSAhdr       hdr;
       uchar                   netmask[4];
       uchar                   attrt[4];
};

/* Summary Link State Advertisement info */
struct Ospfsumm {
       uchar   flag;   /* always zero */
       uchar   metric[3];
};

struct OspfsummLSA {
       struct OspfLSAhdr       hdr;
       uchar                   netmask[4];
       struct Ospfsumm         lsa;
};

/* AS external Link State Advertisement info */
struct OspfASext {
       uchar   flag;   /* external */
       uchar   metric[3];
       uchar   fwdaddr[4];
       uchar   exrttag[4];
};

struct OspfASextLSA {
       struct OspfLSAhdr       hdr;
       uchar                   netmask[4];
       struct OspfASext        lsa;
};

/* OSPF Link State Update Packet */
struct OspfLSupdpkt {
       uchar   lsacnt[4];
       union {
               uchar                   hdr[1];
               struct OspfrtLSA        rt[1];
               struct OspfntLSA        nt[1];
               struct OspfsummLSA      sum[1];
               struct OspfASextLSA     as[1];
       };
};

char*
seprintospflsaheader(char *p, char *e, struct OspfLSAhdr *h)
{
       return seprint(p, e, "age %d opt %ux type %ux lsid %V adv_rt %V seqno %ux c %4.4ux l %d",
               NetS(h->lsage), h->options&0xff, h->lstype,
               h->lsid, h->advtrt, NetL(h->lsseqno), NetS(h->lscksum),
               NetS(h->lsalen));
}

/* OSPF Database Description Packet */
struct OspfDDpkt {
       uchar   intMTU[2];
       uchar   options;
       uchar   bits;
       uchar   DDseqno[4];
       struct OspfLSAhdr       hdr[1];         /* LSA headers... */
};

char*
seprintospfdatadesc(char *p, char *e, void *a, int len)
{
       int nlsa, i;
       struct OspfDDpkt *g;

       g = (struct OspfDDpkt *)a;
       nlsa = len/sizeof(struct OspfLSAhdr);
       for (i=0; i<nlsa; i++) {
               p = seprint(p, e, "lsa%d(", i);
               p = seprintospflsaheader(p, e, &(g->hdr[i]));
               p = seprint(p, e, ")");
       }
       return seprint(p, e, ")");
}

char*
seprintospflsupdate(char *p, char *e, void *a, int len)
{
       int nlsa, i;
       struct OspfLSupdpkt *g;
       struct OspfLSAhdr *h;

       g = (struct OspfLSupdpkt *)a;
       nlsa = NetL(g->lsacnt);
       h = (struct OspfLSAhdr *)(g->hdr);
       p = seprint(p, e, "%d-%s(", nlsa, ospfpkttype(OSPFlsupdate));

       switch(h->lstype) {
       case LSARouter:
               {
/*                      struct OspfrtLSA *h;
*/
               }
               break;
       case LSANetwork:
               {
                       struct OspfntLSA *h;

                       for (i=0; i<nlsa; i++) {
                               h = &(g->nt[i]);
                               p = seprint(p, e, "lsa%d(", i);
                               p = seprintospflsaheader(p, e, &(h->hdr));
                               p = seprint(p, e, " mask %V attrt %V)",
                                       h->netmask, h->attrt);
                       }
               }
               break;
       case LSASummN:
       case LSASummR:
               {
                       struct OspfsummLSA *h;

                       for (i=0; i<nlsa; i++) {
                               h = &(g->sum[i]);
                               p = seprint(p, e, "lsa%d(", i);
                               p = seprintospflsaheader(p, e, &(h->hdr));
                               p = seprint(p, e, " mask %V met %d)",
                                       h->netmask, Net3(h->lsa.metric));
                       }
               }
               break;
       case LSAASext:
               {
                       struct OspfASextLSA *h;

                       for (i=0; i<nlsa; i++) {
                               h = &(g->as[i]);
                               p = seprint(p, e, " lsa%d(", i);
                               p = seprintospflsaheader(p, e, &(h->hdr));
                               p = seprint(p, e, " mask %V extflg %1.1ux met %d fwdaddr %V extrtflg %ux)",
                                       h->netmask, h->lsa.flag, Net3(h->lsa.metric),
                                       h->lsa.fwdaddr, NetL(h->lsa.exrttag));
                       }
               }
               break;
       default:
               p = seprint(p, e, "Not an LS update, lstype %d ", h->lstype);
               p = seprint(p, e, " %.*H", len>64?64:len, a);
               break;
       }
       return seprint(p, e, ")");
}

char*
seprintospflsack(char *p, char *e, void *a, int len)
{
       int nlsa, i;
       struct OspfLSAhdr *h;

       h = (struct OspfLSAhdr *)a;
       nlsa = len/sizeof(struct OspfLSAhdr);
       p = seprint(p, e, "%d-%s(", nlsa, ospfpkttype(OSPFlsack));
       for (i=0; i<nlsa; i++) {
               p = seprint(p, e, " lsa%d(", i);
               p = seprintospflsaheader(p, e, &(h[i]));
               p = seprint(p, e, ")");
       }
       return seprint(p, e, ")");
}

int
p_seprint(Msg *m)
{
       Ospfpkt *ospf;
       int len, x;
       char *p, *e;

       len = m->pe - m->ps;
       if(len < OSPF_HDRSIZE)
               return -1;
       p = m->p;
       e = m->e;

       /* adjust packet size */
       ospf = (Ospfpkt*)m->ps;
       x = NetS(ospf->length);
       if(x < len)
               return -1;
       x -= OSPF_HDRSIZE;

       p = seprint(p, e, "ver=%d type=%d len=%d r=%V a=%V c=%4.4ux %s ",
               ospf->version, ospf->type, x,
               ospf->router, ospf->area, NetS(ospf->sum),
               ospfauth(ospf));

       switch (ospf->type) {
       case OSPFhello:
               p = seprintospfhello(p, e, ospf->data);
               break;
       case OSPFdd:
               p = seprintospfdatadesc(p, e, ospf->data, x);
               break;
       case OSPFlsrequest:
               p = seprint(p, e, " %s->", ospfpkttype(ospf->type));
               goto Default;
       case OSPFlsupdate:
               p = seprintospflsupdate(p, e, ospf->data, x);
               break;
       case OSPFlsack:
               p = seprintospflsack(p, e, ospf->data, x);
               break;
       default:
Default:
               p = seprint(p, e, " data=%.*H", x>64?64:x, ospf->data);
       }
       m->p = p;
       m->pr = nil;
       return 0;
}

Proto ospf =
{
       "ospf",
       nil,
       nil,
       p_seprint,
       nil,
       nil,
       nil,
       defaultframer,
};