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

typedef struct Hdr      Hdr;
struct Hdr
{       uchar   type;
       uchar   code;
       uchar   cksum[2];       /* Checksum */
       uchar   data[1];
};

enum
{
       ICMPLEN=        4,
};

enum
{
       Ot,     /* type */
       Op,     /* next protocol */
};

static Field p_fields[] =
{
       {"t",           Fnum,   Ot,     "type", } ,
       {0}
};

enum
{
       EchoRep=        0,
       Unreachable=    3,
       SrcQuench=      4,
       Redirect=       5,
       EchoReq=        8,
       TimeExceed=     11,
       ParamProb=      12,
       TSreq=          13,
       TSrep=          14,
       InfoReq=        15,
       InfoRep=        16,
};

static Mux p_mux[] =
{
       {"ip",  Unreachable, },
       {"ip",  SrcQuench, },
       {"ip",  Redirect, },
       {"ip",  TimeExceed, },
       {"ip",  ParamProb, },
       {0},
};

char *icmpmsg[256] =
{
[EchoRep]       "EchoRep",
[Unreachable]   "Unreachable",
[SrcQuench]     "SrcQuench",
[Redirect]      "Redirect",
[EchoReq]       "EchoReq",
[TimeExceed]    "TimeExceed",
[ParamProb]     "ParamProb",
[TSreq]         "TSreq",
[TSrep]         "TSrep",
[InfoReq]       "InfoReq",
[InfoRep]       "InfoRep",
};

static void
p_compile(Filter *f)
{
       if(f->op == '='){
               compile_cmp(icmp.name, f, p_fields);
               return;
       }
       if(strcmp(f->s, "ip") == 0){
               f->pr = p_mux->pr;
               f->subop = Op;
               return;
       }
       sysfatal("unknown icmp field or protocol: %s", f->s);
}

static int
p_filter(Filter *f, Msg *m)
{
       Hdr *h;

       if(m->pe - m->ps < ICMPLEN)
               return 0;

       h = (Hdr*)m->ps;
       m->ps += ICMPLEN;

       switch(f->subop){
       case Ot:
               if(h->type == f->ulv)
                       return 1;
               break;
       case Op:
               switch(h->type){
               case Unreachable:
               case TimeExceed:
               case SrcQuench:
               case Redirect:
               case ParamProb:
                       m->ps += 4;
                       return 1;
               }
       }
       return 0;
}

static int
p_seprint(Msg *m)
{
       Hdr *h;
       char *tn;
       char *p = m->p;
       char *e = m->e;
       ushort cksum2, cksum;

       h = (Hdr*)m->ps;
       m->ps += ICMPLEN;
       m->pr = &dump;

       if(m->pe - m->ps < ICMPLEN)
               return -1;

       tn = icmpmsg[h->type];
       if(tn == nil)
               p = seprint(p, e, "t=%ud c=%d ck=%4.4ux", h->type,
                       h->code, (ushort)NetS(h->cksum));
       else
               p = seprint(p, e, "t=%s c=%d ck=%4.4ux", tn,
                       h->code, (ushort)NetS(h->cksum));
       if(Cflag){
               cksum = NetS(h->cksum);
               h->cksum[0] = 0;
               h->cksum[1] = 0;
               cksum2 = ~ptclbsum((uchar*)h, m->pe - m->ps + ICMPLEN) & 0xffff;
               if(cksum != cksum2)
                       p = seprint(p,e, " !ck=%4.4ux", cksum2);
       }
       switch(h->type){
       case EchoRep:
       case EchoReq:
               m->ps += 4;
               p = seprint(p, e, " id=%ux seq=%ux",
                       NetS(h->data), NetS(h->data+2));
               break;
       case TSreq:
       case TSrep:
               m->ps += 12;
               p = seprint(p, e, " orig=%ud rcv=%ux xmt=%ux",
                       NetL(h->data), NetL(h->data+4),
                       NetL(h->data+8));
               m->pr = nil;
               break;
       case InfoReq:
       case InfoRep:
               break;
       case Unreachable:
       case TimeExceed:
       case SrcQuench:
               m->ps += 4;
               m->pr = &ip;
               break;
       case Redirect:
               m->ps += 4;
               m->pr = &ip;
               p = seprint(p, e, "gw=%V", h->data);
               break;
       case ParamProb:
               m->ps += 4;
               m->pr = &ip;
               p = seprint(p, e, "ptr=%2.2ux", h->data[0]);
               break;
       }
       m->p = p;
       return 0;
}

Proto icmp =
{
       "icmp",
       p_compile,
       p_filter,
       p_seprint,
       p_mux,
       "%lud",
       p_fields,
       defaultframer,
};