/*
 * $Id: Vflow.c,v 1.5 2004/12/13 11:13:18 jeffm Exp $
 *
 * Example script,
 *    vf = vflow.new()
 *    vf.open(afile)
 *    vf.find() { |r|
 *      # do something with entry.
 *    }
 *    vf.close()
 *
 *
 */
#include "ruby.h"
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <ftconfig.h>
#include <ftlib.h>
#include <strings.h>

typedef struct vf_data_struct {
  char *current_file;
  int fd;
  struct ftio ftio_data;
  struct ftver ftv;
  struct fts3rec_offsets ftoffsets;
} vf_type;

static VALUE vf_info_free(void *p) {
  xfree(((vf_type *)p)->current_file);
  xfree(p);
}

/* Mapping acsii string to values.
 * Should have been able to use
 *   struct ftxfield_table ftxfield_table[]
 * from flow-tools lib/ftxfield.c but this is not
 * in the same order as
 *    struct fts3rec_offsets
 * in flow-tool's ftlib.h which make calculation
 * for converstions difficult see
 * vfr_init() and vfr_fts3rec_to_vfrec below
 */
struct vfr_mapfields {
  char *name;
  u_int64 val;
  VALUE (*getter)();
  VALUE (*setter)();
};

// needed by libft.a
int debug = 0;

// Are these type identifiers?
VALUE vflow;
VALUE vflowrec;

/********* vflowrec ************
 * vflowrec is a pure ruby object to hold
 * the infomation normally held by
 * struct fts3rec_all in flow-tools
 */

/* private function to create a vfrec
 * input:
 *  struct ftio
 *  struct fts3rec_offsets
 *
 * returns:
 *  ruby object vfrec
 */

/*********** vflowrec ******/
typedef struct vfr_data_struct {
  char *rec;
  struct ftio ftio_data;
  struct fts3rec_offsets offsets;
} vfr_type;

void vfr_copy_data(VALUE vfr_obj, char *r, struct ftio *fdata, struct fts3rec_offsets *o) {
  vfr_type  *info;
  Data_Get_Struct(vfr_obj, vfr_type, info);
  info->rec = xmalloc(fdata->rec_size);
  memcpy(info->rec,r, fdata->rec_size);
  memcpy(&info->ftio_data,fdata,sizeof(struct ftio));
  memcpy(&info->offsets, o, sizeof(struct fts3rec_offsets));
}

static VALUE vfr_info_free(void *p) {
  xfree(((vfr_type *)p)->rec);
  xfree(p);
}

//initialize()
static VALUE vfr_init(VALUE rbself) {
  return rbself;
}

static VALUE vfr_alloc(VALUE klass) {
  vfr_type *info = xmalloc(sizeof(vfr_type));
  VALUE vfr_info = Data_Wrap_Struct(klass, NULL, vfr_info_free, info);
  return vfr_info;
}

// new()
static VALUE vfr_new(VALUE klass) {
  VALUE obj = rb_funcall2(klass, rb_intern("allocate"), 0, 0);
  rb_obj_call_init(obj, 0, NULL);
  return obj;
}

#define VFR_EXTRACT8(INFO,XFIELD,NAME)   Data_Get_Struct(rbself, vfr_type, INFO);\
  if (!ftio_check_xfield(&INFO->ftio_data, XFIELD)) {\
    return INT2NUM(*(u_int8*)(INFO->rec + (INFO->offsets).NAME));\
  }\
  return Qnil;\

#define VFR_EXTRACT16(INFO,XFIELD,NAME)   Data_Get_Struct(rbself, vfr_type, INFO);\
  if (!ftio_check_xfield(&INFO->ftio_data, XFIELD))\
    return INT2NUM(*(u_int16*)(INFO->rec + (INFO->offsets).NAME));\
  return Qnil;\

#define VFR_EXTRACT32(INFO,XFIELD,NAME)   Data_Get_Struct(rbself, vfr_type, INFO);\
  if (!ftio_check_xfield(&INFO->ftio_data, XFIELD)) {\
    u_int64 tmp64 = *(u_int32*)(INFO->rec + (INFO->offsets).NAME);\
    return INT2NUM(tmp64);\
  }\
  return Qnil;\

#define VFR_EXTRACTADDR(INFO,XFIELD,NAME)   Data_Get_Struct(rbself, vfr_type, INFO);\
  if (!ftio_check_xfield(&INFO->ftio_data, XFIELD)) {\
    return INT2NUM(*(u_int32*)(INFO->rec + (INFO->offsets).NAME));\
  }\
  return Qnil;\


// vfr_unix_secs
static VALUE vfr_unix_secs(VALUE rbself) {
  vfr_type *info;
  VFR_EXTRACT32(info,FT_XFIELD_UNIX_SECS,unix_secs)
}

// vfr_unix_nsecs
static VALUE vfr_unix_nsecs(VALUE rbself) {
  vfr_type *info;
  VFR_EXTRACT32(info,FT_XFIELD_UNIX_NSECS,unix_nsecs);
}

// vfr_sysuptime
static VALUE vfr_sysuptime(VALUE rbself) {
  vfr_type *info;
  VFR_EXTRACT32(info,FT_XFIELD_SYSUPTIME,sysUpTime);
}

static VALUE vfr_exaddr(VALUE rbself) {
  vfr_type *info;
  VFR_EXTRACTADDR(info,FT_XFIELD_EXADDR,exaddr)
}

static VALUE vfr_srcaddr(VALUE rbself) {
  vfr_type *info;
  VFR_EXTRACTADDR(info,FT_XFIELD_SRCADDR,srcaddr)
}

static VALUE vfr_dstaddr(VALUE rbself) {
  vfr_type *info;
  VFR_EXTRACTADDR(info,FT_XFIELD_DSTADDR,dstaddr)
}
static VALUE vfr_nexthop(VALUE rbself) {
  vfr_type *info;
  VFR_EXTRACTADDR(info,FT_XFIELD_NEXTHOP,nexthop)
}

static VALUE vfr_input(VALUE rbself) {
  vfr_type *info;
  VFR_EXTRACT16(info, FT_XFIELD_INPUT,input)
}

static VALUE vfr_output(VALUE rbself) {
  vfr_type *info;
  VFR_EXTRACT16(info, FT_XFIELD_OUTPUT,output)
}

static VALUE vfr_dflows(VALUE rbself) {
  vfr_type *info;
  VFR_EXTRACT32(info, FT_XFIELD_DFLOWS, dFlows)
}

static VALUE vfr_dpkts(VALUE rbself) {
  vfr_type *info;
  VFR_EXTRACT32(info, FT_XFIELD_DPKTS, dPkts);
}

static VALUE vfr_doctets(VALUE rbself) {
  vfr_type *info;
  VFR_EXTRACT32(info, FT_XFIELD_DPKTS, dOctets);
}

static VALUE vfr_first(VALUE rbself) {
  vfr_type *info;
  VFR_EXTRACT32(info, FT_XFIELD_FIRST, First);
}

static VALUE vfr_last(VALUE rbself) {
  vfr_type *info;
  VFR_EXTRACT32(info, FT_XFIELD_LAST, Last);
}

static VALUE vfr_srcport(VALUE rbself) {
  vfr_type *info;
  VFR_EXTRACT16(info, FT_XFIELD_SRCPORT, srcport);
}

static VALUE vfr_dstport(VALUE rbself) {
  vfr_type *info;
  VFR_EXTRACT16(info, FT_XFIELD_DSTPORT, dstport);
}

static VALUE vfr_prot(VALUE rbself) {
  vfr_type *info;
  VFR_EXTRACT8(info, FT_XFIELD_PROT, prot);
}

static VALUE vfr_tos(VALUE rbself) {
  vfr_type *info;
  VFR_EXTRACT8(info, FT_XFIELD_TOS, tos);
}

static VALUE vfr_tcp_flags(VALUE rbself) {
  vfr_type *info;
  VFR_EXTRACT8(info, FT_XFIELD_TCP_FLAGS, tcp_flags);
}

static VALUE vfr_engine_type(VALUE rbself) {
  vfr_type *info;
  VFR_EXTRACT8(info, FT_XFIELD_ENGINE_TYPE, engine_type);
}

static VALUE vfr_engine_id(VALUE rbself) {
  vfr_type *info;
  VFR_EXTRACT8(info, FT_XFIELD_ENGINE_ID, engine_id);
}

static VALUE vfr_src_mask(VALUE rbself) {
  vfr_type *info;
  VFR_EXTRACT8(info, FT_XFIELD_SRC_MASK, src_mask);
}

static VALUE vfr_dst_mask(VALUE rbself) {
  vfr_type *info;
  VFR_EXTRACT8(info, FT_XFIELD_DST_MASK, dst_mask);
}

static VALUE vfr_src_as(VALUE rbself) {
  vfr_type *info;
  VFR_EXTRACT16(info, FT_XFIELD_SRC_AS, src_as);
}

static VALUE vfr_dst_as(VALUE rbself) {
  vfr_type *info;
  VFR_EXTRACT16(info, FT_XFIELD_DST_AS, dst_as);
}

static VALUE vfr_in_encaps(VALUE rbself) {
  vfr_type *info;
  VFR_EXTRACT8(info, FT_XFIELD_IN_ENCAPS, in_encaps);
}

static VALUE vfr_out_encaps(VALUE rbself) {
  vfr_type *info;
  VFR_EXTRACT8(info, FT_XFIELD_OUT_ENCAPS, out_encaps);
}

static VALUE vfr_peer_nexthop(VALUE rbself) {
  vfr_type *info;
  VFR_EXTRACTADDR(info, FT_XFIELD_PEER_NEXTHOP, peer_nexthop);
}

static VALUE vfr_router_sc(VALUE rbself) {
  vfr_type *info;
  VFR_EXTRACT32(info, FT_XFIELD_ROUTER_SC, router_sc);
}

static VALUE vfr_src_tag(VALUE rbself) {
  vfr_type *info;
  VFR_EXTRACT32(info, FT_XFIELD_SRC_TAG, src_tag);
}

static VALUE vfr_dst_tag(VALUE rbself) {
  vfr_type *info;
  VFR_EXTRACT32(info, FT_XFIELD_DST_TAG, dst_tag);
}

static VALUE vfr_extra_pkts(VALUE rbself) {
  vfr_type *info;
  VFR_EXTRACT32(info, FT_XFIELD_EXTRA_PKTS, extra_pkts);
}

static VALUE vfr_marked_tos(VALUE rbself) {
  vfr_type *info;
  VFR_EXTRACT8(info, FT_XFIELD_MARKED_TOS, marked_tos);
}

static VALUE vfr_start_time(VALUE rbself) {
  vfr_type *info;

  Data_Get_Struct(rbself, vfr_type, info);
  
  if (!ftio_check_xfield(&info->ftio_data,
			 FT_XFIELD_SYSUPTIME |
			 FT_XFIELD_UNIX_SECS)) {
    struct fttime ftt = ftltime(*(u_int32*)(info->rec + (info->offsets).sysUpTime),
				*(u_int32*)(info->rec + (info->offsets).unix_secs),
				*(u_int32*)(info->rec + (info->offsets).unix_nsecs),
				*(u_int32*)(info->rec + (info->offsets).First));
    return INT2NUM(ftt.secs);
  }
  return Qnil;
}

static VALUE vfr_end_time(VALUE rbself) {
  vfr_type *info;

  Data_Get_Struct(rbself, vfr_type, info);
  
  if (!ftio_check_xfield(&info->ftio_data,
			 FT_XFIELD_SYSUPTIME |
			 FT_XFIELD_UNIX_SECS)) {
    struct fttime ftt = ftltime(*(u_int32*)(info->rec + (info->offsets).sysUpTime),
				*(u_int32*)(info->rec + (info->offsets).unix_secs),
				*(u_int32*)(info->rec + (info->offsets).unix_nsecs),
				*(u_int32*)(info->rec + (info->offsets).First));
    return INT2NUM(ftt.secs);
  }
  return Qnil;
}


// declare methods to ruby
void Init_Vflowrec() {
  vflowrec = rb_define_class("Vflowrec", rb_cObject);
#if HAVE_RB_DEFINE_ALLOC_FUNC
  rb_define_alloc_func(vflowrec, vfr_alloc);
#else
  rb_define_singleton_method(vflowrec, "allocate", vfr_alloc, 0);
#endif
  rb_define_singleton_method(vflowrec, "new", vfr_new, 0);
  rb_define_method(vflowrec, "initialize", vfr_init, 0);
  rb_define_method(vflowrec, "unix_secs", vfr_unix_secs, 0);
  rb_define_method(vflowrec, "unix_nsecs", vfr_unix_nsecs, 0);
  rb_define_method(vflowrec, "sysuptime", vfr_sysuptime, 0);
  rb_define_method(vflowrec, "exaddr", vfr_exaddr, 0);
  rb_define_method(vflowrec, "srcaddr", vfr_srcaddr, 0);
  rb_define_method(vflowrec, "dstaddr", vfr_dstaddr,  0);
  rb_define_method(vflowrec, "nexthop", vfr_nexthop, 0);
  rb_define_method(vflowrec, "input", vfr_input, 0);
  rb_define_method(vflowrec, "output", vfr_output, 0);
  rb_define_method(vflowrec, "dflows", vfr_dflows, 0);
  rb_define_method(vflowrec, "dpkts", vfr_dpkts, 0);
  rb_define_method(vflowrec, "doctets", vfr_doctets, 0);
  rb_define_method(vflowrec, "first", vfr_first, 0);
  rb_define_method(vflowrec, "last", vfr_last, 0);
  rb_define_method(vflowrec, "srcport", vfr_srcport, 0);
  rb_define_method(vflowrec, "dstport", vfr_dstport, 0);
  rb_define_method(vflowrec, "prot", vfr_prot, 0);
  rb_define_method(vflowrec, "tos", vfr_tos, 0);
  rb_define_method(vflowrec, "tcp_flags", vfr_tcp_flags, 0);
  rb_define_method(vflowrec, "engine_type", vfr_engine_type, 0);
  rb_define_method(vflowrec, "engine_id", vfr_engine_id, 0);
  rb_define_method(vflowrec, "src_mask", vfr_src_mask, 0);
  rb_define_method(vflowrec, "dst_mask", vfr_dst_mask, 0);
  rb_define_method(vflowrec, "src_as", vfr_src_as, 0);
  rb_define_method(vflowrec, "dst_as", vfr_dst_as, 0);
  rb_define_method(vflowrec, "in_encaps", vfr_in_encaps, 0);
  rb_define_method(vflowrec, "out_encaps", vfr_out_encaps, 0);
  rb_define_method(vflowrec, "peer_nexthop", vfr_peer_nexthop, 0);
  rb_define_method(vflowrec, "router_sc", vfr_router_sc, 0);
  rb_define_method(vflowrec, "src_tag", vfr_src_tag, 0);
  rb_define_method(vflowrec, "dst_tag", vfr_dst_tag, 0);
  rb_define_method(vflowrec, "extra_pkts", vfr_extra_pkts, 0);
  rb_define_method(vflowrec, "marked_tos", vfr_marked_tos, 0);
  rb_define_method(vflowrec, "start_time", vfr_start_time, 0);
  rb_define_method(vflowrec, "end_time", vfr_end_time, 0);

}

/*********** vflow ********/
// initialize()
static VALUE vf_init(VALUE rbself) {
  return rbself;
}

static VALUE vf_alloc(VALUE klass) {
  vf_type *info = xmalloc(sizeof(vf_type));
  info->current_file = NULL;    /* set to sensible defaults */
  VALUE vf_info = Data_Wrap_Struct(klass, NULL, vf_info_free, info);
  return vf_info;
}

// new()
static VALUE vf_new(VALUE klass) {
  VALUE obj = rb_funcall2(klass, rb_intern("allocate"), 0, 0);
  rb_obj_call_init(obj, 0, NULL);
  return obj;
}

// header() - return header of current file
static VALUE vf_header(VALUE rbself) {

}

// currentfile() 
static VALUE vf_currentfile(VALUE rbself) {
  vf_type *info;
  Data_Get_Struct(rbself, vf_type, info);
  return rb_str_new2(info->current_file);
}

/* open(string)
 * opens a single file for reading
 */
static VALUE vf_open(VALUE rbself, VALUE file) {
  struct stat statdata;
  int fd;
  vf_type *info;

  Data_Get_Struct(rbself, vf_type, info);
  if (rb_type(file) == T_STRING) {
    info->current_file = STR2CSTR(file);

    /* determine file type */
    if ((fd = open(info->current_file, O_RDONLY, 0)) < 0)
      rb_raise(rb_eIOError, "Cannot open file: %s", info->current_file);
    info->fd = fd;
    if (fstat(fd, & statdata) < 0)
      rb_raise(rb_eIOError, "Cannot stat file: %s", info->current_file);

    if (!S_ISREG(statdata.st_mode)) {
      rb_raise(rb_eIOError, "File Type (0x%x) not handled for file %s",
	       statdata.st_mode, info->current_file);
    }

    if (ftio_init(&info->ftio_data, fd, FT_IO_FLAG_READ) < 0)
      rb_raise(rb_eIOError, "ftio_init() error");

    // store values needed to make calls to ftio_read and alike
    ftio_get_ver(&info->ftio_data, &info->ftv);
    fts3rec_compute_offsets(&info->ftoffsets, &info->ftv);

    return Qnil;
  }
  rb_raise(rb_eTypeError,"Not a string");
}

// close one file
static VALUE vf_close(VALUE rbself) {
  vf_type *info;

  Data_Get_Struct(rbself, vf_type, info);
  ftio_close(&info->ftio_data);  // FIXME: should check return code
  close(info->fd);
  free(info->current_file);
  info->fd = 0;
}

// next() - return next entry in file
static VALUE vf_next(VALUE rbself) {
  char *ent;
  struct fts3rec_all cur;
  vf_type *info;
  VALUE vfr;

  Data_Get_Struct(rbself, vf_type, info);
  
  if ((ent = ftio_read(&info->ftio_data)) == NULL) 
    return Qnil;
  // create flow record object
  //  return vfr_fts3rec_to_vfrec(ent, &info->ftio_data, &info->ftoffsets);
  vfr = rb_funcall2(vflowrec,rb_intern("new"),0,0);
  vfr_copy_data(vfr, ent, &info->ftio_data, &info->ftoffsets);
  return vfr;
}


/* for each recoard call block
 * Examples,
 *    each() {|flowrec| ....}
 */
static VALUE vf_each(VALUE rbself) {
  VALUE vfr_rec;

  // debugging
  //ftio_header_print(&info->ftio_data, stdout, 'f');
  while ((vfr_rec = vf_next(rbself)) != Qnil) {
    rb_yield(vfr_rec);     // call block
  }
  return Qnil;
}


void Init_Vflow() {
  Init_Vflowrec();
  vflow = rb_define_class("Vflow", rb_cObject);
#if HAVE_RB_DEFINE_ALLOC_FUNC
  rb_define_alloc_func(vflow, vf_alloc);
#else
  rb_define_singleton_method(vflow, "allocate", vf_alloc, 0);
#endif
  rb_define_singleton_method(vflow, "new", vf_new, 0);
  rb_define_method(vflow, "initialize", vf_init, 0);
  rb_define_method(vflow, "currentfile", vf_currentfile, 0);
  rb_define_method(vflow, "open", vf_open, 1);
  rb_define_method(vflow, "close", vf_close, 0);
  rb_define_method(vflow, "next", vf_next, 0);
  rb_define_method(vflow, "each", vf_each, 0);
}
