/*
 *  packet.c
 *
 *  $Id: packet.c,v 1.4 2000/08/13 06:56:15 fukusima Exp $
 *
 *  Copyright (C) 1998-2000  Masaki Fukushima
 */

#include "ruby.h"
#include "ruby_pcap.h"
#include <sys/socket.h>
#include <net/if.h>
#include <netinet/if_ether.h>

#define DL_HDR(pkt)     ((u_char *)LAYER2_HDR(pkt))
#define DL_DATA(pkt)    ((u_char *)LAYER3_HDR(pkt))

VALUE cPacket;
static VALUE mMarshal;
int id_load;
int id_dump;

/* called from GC */
static void
free_packet(pkt)
     struct packet_object *pkt;
{
    DEBUG_PRINT("free_packet");
    free(pkt);
}

/* called from GC */
static void
mark_packet(pkt)
     struct packet_object *pkt;
{
    DEBUG_PRINT("mark_packet");
    if (pkt->udata != Qnil)
        rb_gc_mark(pkt->udata);
}

struct datalink_type {
    int nltype_off;     /* offset of network-layer type field */
    int nl_off;         /* offset of network-layer header */
};

static struct datalink_type datalinks[] = {
#if 0
    {  0,  4 }, /*  0: DLT_NULL */
#else
    { -1,  4 }, /*  0: DLT_NULL */
#endif
    { 12, 14 }, /*  1: DLT_EN10MB */
    { -1, -1 }, /*  2: DLT_EN3MB */
    { -1, -1 }, /*  3: DLT_AX25 */
    { -1, -1 }, /*  4: DLT_PRONET */
    { -1, -1 }, /*  5: DLT_CHAOS */
    { 20, 22 }, /*  6: DLT_IEEE802 */
    { -1, -1 }, /*  7: DLT_ARCNET */
    { -1, 16 }, /*  8: DLT_SLIP */
    {  2,  4 }, /*  9: DLT_PPP */
#ifndef PCAP_FDDIPAD
    { 19, 21 }, /* 10: DLT_FDDI */
#else
    { 19 + PCAP_FDDIPAD, 21 + PCAP_FDDIPAD },
#endif
    {  6,  8 }, /* 11: DLT_ATM_RFC1483 */
    { -1,  0 }, /* 12: DLT_RAW */
    { -1, 24 }, /* 13: DLT_SLIP_BSDOS */
    {  5, 24 }  /* 14: DLT_PPP_BSDOS */
#define DATALINK_MAX 14
};

VALUE
new_packet(data, pkthdr, dl_type)
     const u_char *data;
     const struct pcap_pkthdr *pkthdr;
     int dl_type;
{
    VALUE class;
    struct packet_object *pkt;
    int nl_off, nl_len, nltype_off, nl_type, pad;

    DEBUG_PRINT("new_packet");

    /* check nework layer type and offset */
    if (dl_type > DATALINK_MAX) {
        rb_raise(ePcapError, "Unknown data-link type (%d)", dl_type);
    }
    nltype_off  = datalinks[dl_type].nltype_off;
    nl_off      = datalinks[dl_type].nl_off;
    if (nl_off < 0) {
        rb_raise(ePcapError, "Unsupported data-link type (%d)", dl_type);
    }
    if (nltype_off == -1) {
        /* assume IP */
        nl_type = ETHERTYPE_IP;
    } else {
        /* assume Ether Type value */
        nl_type = ntohs(*(u_short *)(data + nltype_off));
    }

    /* alloc memory and setup packet_object */
    pad = nl_off % 4;   /* align network layer header */
    pkt = xmalloc(sizeof(*pkt) + pad + pkthdr->caplen);
    pkt->hdr.version    = PACKET_MARSHAL_VERSION;
    pkt->hdr.flags      = 0;
    pkt->hdr.dl_type    = dl_type;
    pkt->hdr.layer3_off = OFF_NONEXIST;
    pkt->hdr.layer4_off = OFF_NONEXIST;
    pkt->hdr.layer5_off = OFF_NONEXIST;
    pkt->hdr.pkthdr     = *pkthdr;
    pkt->data = (u_char *)pkt + sizeof(*pkt) + pad;
    pkt->udata = Qnil;
    memcpy(pkt->data, data, pkthdr->caplen);

    nl_len = pkthdr->caplen - nl_off;
    if (nl_off >= 0 && nl_len > 0)
        pkt->hdr.layer3_off = nl_off;

    /* setup upper layer */
    class = cPacket;
    if (pkt->hdr.layer3_off != OFF_NONEXIST) {
        switch (nl_type) {
        case ETHERTYPE_IP:
            class = setup_ip_packet(pkt, nl_len);
            break;
        }
    }
#if DEBUG
    if (ruby_debug && TYPE(class) != T_CLASS) {
        rb_fatal("not class");
    }
#endif
    return Data_Wrap_Struct(class, mark_packet, free_packet, pkt);
}

static VALUE
packet_load(class, str)
     VALUE class;
     VALUE str;
{
    struct packet_object *pkt = NULL;
    struct packet_object_header *hdr;
    int version;
    u_char *str_ptr;

    DEBUG_PRINT("packet_load");

    str_ptr = RSTRING_PTR(str);
    hdr = (struct packet_object_header *)str_ptr;
    version = hdr->version;
    if (version == PACKET_MARSHAL_VERSION) {
        bpf_u_int32 caplen;
        u_short layer3_off;
        int pad;

        caplen = ntohl(hdr->pkthdr.caplen);
        layer3_off = ntohs(hdr->layer3_off);
        pad = layer3_off % 4;   /* align network layer header */
        pkt = (struct packet_object *)xmalloc(sizeof(*pkt) + pad + caplen);

        pkt->hdr.version                = PACKET_MARSHAL_VERSION;
        pkt->hdr.flags                  = hdr->flags;
        pkt->hdr.dl_type                = hdr->dl_type;
        pkt->hdr.layer3_off             = ntohs(hdr->layer3_off);
        pkt->hdr.layer4_off             = ntohs(hdr->layer4_off);
        pkt->hdr.layer5_off             = ntohs(hdr->layer5_off);
        pkt->hdr.pkthdr.ts.tv_sec       = ntohl(hdr->pkthdr.ts.tv_sec);
        pkt->hdr.pkthdr.ts.tv_usec      = ntohl(hdr->pkthdr.ts.tv_usec);
        pkt->hdr.pkthdr.caplen          = ntohl(hdr->pkthdr.caplen);
        pkt->hdr.pkthdr.len             = ntohl(hdr->pkthdr.len);

        pkt->data = (u_char *)pkt + sizeof(*pkt) + pad;
        memcpy(pkt->data, str_ptr + sizeof(*hdr), caplen);
        if (PKTFLAG_TEST(pkt, POH_UDATA)) {
            int l = sizeof(*hdr) + caplen;
            VALUE ustr = rb_str_substr(str, l, RSTRING_LEN(str) - l);
            pkt->udata = rb_funcall(mMarshal, id_load, 1, ustr);
        } else {
            pkt->udata = Qnil;
        }
        PKTFLAG_SET(pkt, POH_UDATA, (pkt->udata != Qnil));
    } else {
        rb_raise(rb_eArgError, "unknown packet marshal version(0x%x)", version);
    }

    if (pkt != NULL)
        return Data_Wrap_Struct(class, mark_packet, free_packet, pkt);
    else
        return Qnil;
}

static VALUE
packet_dump(self, limit)
     VALUE self;
     VALUE limit;
{
    struct packet_object *pkt;
    struct packet_object_header hdr;
    VALUE str;

    DEBUG_PRINT("packet_dump");
    GetPacket(self, pkt);

    hdr.version                 = PACKET_MARSHAL_VERSION;
    hdr.flags                   = pkt->hdr.flags;
    hdr.dl_type                 = pkt->hdr.dl_type;
    hdr.layer3_off              = htons(pkt->hdr.layer3_off);
    hdr.layer4_off              = htons(pkt->hdr.layer4_off);
    hdr.layer5_off              = htons(pkt->hdr.layer5_off);
    hdr.pkthdr.ts.tv_sec        = htonl(pkt->hdr.pkthdr.ts.tv_sec);
    hdr.pkthdr.ts.tv_usec       = htonl(pkt->hdr.pkthdr.ts.tv_usec);
    hdr.pkthdr.caplen           = htonl(pkt->hdr.pkthdr.caplen);
    hdr.pkthdr.len              = htonl(pkt->hdr.pkthdr.len);

    str = rb_str_new((char *)&hdr, sizeof(hdr));
    rb_str_cat(str, pkt->data, pkt->hdr.pkthdr.caplen);
    if (pkt->udata != Qnil) {
        VALUE ustr;
        ustr = rb_funcall(mMarshal, id_dump, 1, pkt->udata);
        rb_str_concat(str, ustr);
    }
    return str;
}

static VALUE
packet_set_udata(self, val)
     VALUE self;
     VALUE val;
{
    struct packet_object *pkt;

    DEBUG_PRINT("packet_set_udata");
    GetPacket(self, pkt);

    pkt->udata = val;
    PKTFLAG_SET(pkt, POH_UDATA, (val != Qnil));
    return val;
}

static VALUE
packet_set_time_i(self, val)
     VALUE self;
     VALUE val;
{
     struct packet_object *pkt;
     unsigned int time_i;

     DEBUG_PRINT("packet_set_time_i");
     GetPacket(self, pkt);
     Check_Type(val, T_FIXNUM);
     time_i = NUM2UINT(val);

     pkt->hdr.pkthdr.ts.tv_sec = time_i;
     return val;
}

static VALUE
packet_match(self, expr)
     VALUE self;
     VALUE expr;
{
    if (IsKindOf(expr, cFilter)) {
        return filter_match(expr, self);
    }
    rb_raise(rb_eArgError, "Not Filter");
}

#define PACKET_METHOD(func, val) \
static VALUE\
(func)(self)\
     VALUE self;\
{\
    struct packet_object *pkt;\
\
    DEBUG_PRINT(#func);\
    GetPacket(self, pkt);\
    return (val);\
}

PACKET_METHOD(packet_get_udata, pkt->udata);
PACKET_METHOD(packet_datalink, INT2FIX(pkt->hdr.dl_type));
PACKET_METHOD(packet_ip, rb_obj_is_kind_of(self, cIPPacket));
PACKET_METHOD(packet_tcp, rb_obj_is_kind_of(self, cTCPPacket));
PACKET_METHOD(packet_udp, rb_obj_is_kind_of(self, cUDPPacket));
PACKET_METHOD(packet_length, UINT32_2_NUM(pkt->hdr.pkthdr.len));
PACKET_METHOD(packet_caplen, UINT32_2_NUM(pkt->hdr.pkthdr.caplen));
PACKET_METHOD(packet_time, rb_time_new(pkt->hdr.pkthdr.ts.tv_sec,
                                       pkt->hdr.pkthdr.ts.tv_usec));
PACKET_METHOD(packet_time_i, rb_int2inum(pkt->hdr.pkthdr.ts.tv_sec));
PACKET_METHOD(packet_raw_data, rb_str_new(pkt->data, pkt->hdr.pkthdr.caplen));

void
Init_packet(void)
{
    DEBUG_PRINT("Init_packet");

    /* define class Packet */
    cPacket = rb_define_class_under(mPcap, "Packet", rb_cObject);

    rb_define_singleton_method(cPacket, "_load", packet_load, 1);
    rb_define_method(cPacket, "_dump", packet_dump, 1);
    /* marshal backward compatibility */
    rb_define_singleton_method(cPacket, "_load_from", packet_load, 1);
    rb_define_method(cPacket, "_dump_to", packet_dump, 1);

    rb_define_method(cPacket, "udata", packet_get_udata, 0);
    rb_define_method(cPacket, "udata=", packet_set_udata, 1);
    rb_define_method(cPacket, "datalink", packet_datalink, 0);
    rb_define_method(cPacket, "ip?", packet_ip, 0);
    rb_define_method(cPacket, "tcp?", packet_tcp, 0);
    rb_define_method(cPacket, "udp?", packet_udp, 0);
    rb_define_method(cPacket, "length", packet_length, 0);
    rb_define_method(cPacket, "size", packet_length, 0);
    rb_define_method(cPacket, "caplen", packet_caplen, 0);
    rb_define_method(cPacket, "time", packet_time, 0);
    rb_define_method(cPacket, "time_i", packet_time_i, 0);
    rb_define_method(cPacket, "time_i=", packet_set_time_i, 1);
    rb_define_method(cPacket, "raw_data", packet_raw_data, 0);
    rb_define_method(cPacket, "=~", packet_match, 1);

    /* mMarshal in ruby/marshal.c is static. Why? */
    mMarshal = rb_eval_string("Marshal");
    id_load = rb_intern("load");
    id_dump = rb_intern("dump");
    Init_ip_packet();
}
