/* 
 * $Source: /b/cvsroot/wu_arl/msr/rp/plugins/testEM/testEM.c,v $
 * $Author: fredk $
 * $Date: 2004/12/22 20:14:34 $
 * $Revision: 1.1 $
 *
 * Copyright (c) 2000 ETH Zurich/Washington University in St. Louis.
 * All rights reserved.
 * 
 * Changes:
 * 2000/11/23 Initial version (keller)
 *
 */

#include "stdinc.h"

#include "testEM.h"

/* function prototypes */
void testEM_init_class();
struct rp_class *testEM_get_class();
struct rp_instance *testEM_create_instance(struct rp_class *, u_int32_t);
void testEM_handle_packet(struct rp_instance *, void *);
void testEM_free_instance(struct rp_instance *);
void testEM_bind_instance(struct rp_instance *);
void testEM_unbind_instance(struct rp_instance *);
int  testEM_handle_msg(struct rp_instance *, void *, u_int8_t, u_int8_t, u_int8_t *);

/* XXX return type int or void */
int testEM  (struct lkm_table *, int, int, struct kernel_plugin_fct_struct *);
int testEM_load(struct lkm_table *, int);
int testEM_unload(struct lkm_table *, int);

MOD_MISC("testEM")

/* Static plugin class structure.
 * Each kernel module has one class structure that is initialized by
 * the plugin and a pointer to it is returned. The class structure can then
 * be used to create plugin instances. Note that this is a structure and 
 * not a pointer. */
static struct rp_class testEM_class;

/* ----------- plugin initialization ----------- */

/* initialize plugin class */
void testEM_init_class() {
  /*
   * Initialize class struct:
   *   classid - user defined
   *   itype   - RP_INTERFACE_TYPE_PKT - for read-only plugins. The handle packet
   *             signature is <handle_packet(struct rp_instance *this, void *pkt)>
   *             RP_INTERFACE_TYPE_HLIST  for plugins that will modify packets or wish
   *             to source or sink data. The signature is
   *              <handle_packet(struct rp_instance *this, void *plist)>
   *   create_instance = function pointer to your create instance method.
   * */
  testEM_class.classid = 200;
  testEM_class.itype   = RP_INTERFACE_TYPE_HLIST;
  testEM_class.create_instance = testEM_create_instance;

  return;
}

/* return the class structure to the caller */
struct rp_class *testEM_get_class() {
  return &testEM_class;
}

/* ----------- class methods ----------- */

/* create and initialize an instance of testEM */
struct rp_instance *
testEM_create_instance(struct rp_class *myclass, u_int32_t instanceid) {
  struct testEM_instance *myinst; 

  /* Must allocate memory for your local instance struct */
  MSR_PLUGIN_MALLOC(myinst,struct testEM_instance *, 
                    sizeof(struct testEM_instance),
                    M_MSR, 
                    M_WAITOK);

  if (myinst == NULL)
    return NULL;

  /* fil in instance pointers to local methods */
  myinst->rootinstance.rpclass         = &testEM_class;
  myinst->rootinstance.handle_packet   = testEM_handle_packet;
  myinst->rootinstance.free_instance   = testEM_free_instance;
  myinst->rootinstance.bind_instance   = testEM_bind_instance;
  myinst->rootinstance.unbind_instance = testEM_unbind_instance;
  myinst->rootinstance.handle_msg      = testEM_handle_msg;

  /* use the instance ID passed in the arguments */
  myinst->rootinstance.instanceid = instanceid;

  myinst->N    = TESTEM_N;
  myinst->QID  = TESTEM_QID;
  myinst->PRIO = TESTEM_PRIO;
  myinst->PN   = TESTEM_PORT;
  myinst->SP   = TESTEM_SPORT;
  myinst->flags     = 0; // No Filtered, No Running
  myinst->pkt_count = 0;

  return (struct rp_instance *)myinst;
}

/* ----------- instance methods ----------- */

int new_event(struct testEM_instance *myinst);
int testem_remfltr(struct testEM_instance *myinst);
int testem_addfltr(struct testEM_instance *myinst);
int testem_mkfltr(struct testEM_instance *, msr_bufhdr_t *);

int new_event(struct testEM_instance *myinst)
{ return (myinst->pkt_count % myinst->N) == 1; }

int testem_mkfltr(struct testEM_instance *myinst, msr_bufhdr_t *hdr)
{
  struct msr_ip_tport *tp;
  struct ip *iph = msr_pkt_iph(hdr);
  MSR_Shim_t *shim = msr_pkt_shim(hdr);
  u_int32_t ovin;
  ovin = msr_shim_get_ivin(shim);

  // first make sure this is not a fragment -- they are not
  // supported by the ONL but lets check anyway
  if (msr_isfrag(iph)) {
    MSR_DEBUG((MSR_DEBUG_PLUGIN | MSR_DEBUG_LEVEL_ERROR,
            "testEM: IP datagram is fragmented! Not supported.\n"));
    return -1;
  }

  /* Note: IP Header fields must be in Network Byte Order */
  tp = (struct msr_ip_tport *)((char *)iph + msr_iphlen(iph));
  myinst->fltr.saddr    = iph->ip_dst.s_addr;
  myinst->fltr.daddr    = iph->ip_src.s_addr;
  myinst->fltr.smask    = myinst->fltr.dmask    = 0xFFFFFFFF;
  myinst->fltr.sp_begin = msr_hasports(iph) ? tp->dport : 0;
  myinst->fltr.sp_end   = msr_hasports(iph) ? tp->dport : 0; /* not used */
  myinst->fltr.dp_begin = msr_hasports(iph) ? tp->sport : 0;
  myinst->fltr.dp_end   = msr_hasports(iph) ? tp->sport : 0; /* not used */
  myinst->fltr.proto    = iph->ip_p;

  myinst->params.fid  = QTBL_QID_INVALID;
  myinst->params.qid  = myinst->QID;
  myinst->params.prio = myinst->PRIO;

  /* Flags and forward key must be in Host Byte Order */
  // ovin = msr_vin_set_sp(ovin, 0);
  ovin = msr_vin_set_pn(ovin, myinst->PN);
  ovin = msr_vin_set_sp(ovin, myinst->SP);
  myinst->fltr.fwdkey = msr_rt_mkfwdkey(myinst->params.qid, ovin);

  myinst->fltr.flags = MSR_CFY_FLAGS_EMATCH;
  myinst->fltr.pad   = 0;

  return 0;
}

int testem_remfltr(struct testEM_instance *myinst)
{
  int result = PLUGIN_MSR_CFY_FREM_FCT (MSR_CFY_ID_EXACT, &myinst->fltr, &myinst->params);
  if (result == MSR_CFY_RESULT_OK) {
    MSR_DEBUG((MSR_DEBUG_PLUGIN | MSR_DEBUG_LEVEL_INFO,
            "testEM: Filter removed: fid %d, qid %d, prio %d\n", 
            myinst->params.fid, myinst->params.qid, myinst->params.prio));
  } else if (result == MSR_CFY_RESULT_NotFound) {
    MSR_DEBUG((MSR_DEBUG_PLUGIN | MSR_DEBUG_LEVEL_INFO,
            "testEM: Filter not found\n"));
  } else if (result == MSR_CFY_RESULT_Free) {
    MSR_DEBUG((MSR_DEBUG_PLUGIN | MSR_DEBUG_LEVEL_INFO,
            "testEM: Filter already removed!\n"));
  } else if (result == MSR_CFY_RESULT_Error) {
    MSR_DEBUG((MSR_DEBUG_PLUGIN | MSR_DEBUG_LEVEL_INFO,
                "testEM: General remove error\n"));
  } else if (result == MSR_CFY_RESULT_InvArgs) {
    MSR_DEBUG((MSR_DEBUG_PLUGIN | MSR_DEBUG_LEVEL_INFO,
            "testEM: One or more arguments are in error\n"));
  } else if (result == MSR_CFY_RESULT_InUse) {
    MSR_DEBUG((MSR_DEBUG_PLUGIN | MSR_DEBUG_LEVEL_INFO,
            "testEM: Filter has a bound plugin, can not remove: fid %d, qid %d, prio %d\n",
            myinst->params.fid, myinst->params.qid, myinst->params.prio));
  } else {
    MSR_DEBUG((MSR_DEBUG_PLUGIN | MSR_DEBUG_LEVEL_INFO,
            "testEM: Unknown error removing filter, %d\n", result));
  }

  return result;
}

int testem_addfltr(struct testEM_instance *myinst)
{
  int result = PLUGIN_MSR_CFY_FADD_FCT(MSR_CFY_ID_EXACT, &myinst->fltr, &myinst->params);
  if (result == MSR_CFY_RESULT_OK) {
    MSR_DEBUG((MSR_DEBUG_PLUGIN | MSR_DEBUG_LEVEL_INFO,
              "testEM: Filter added, fid %d, qid %d, prio %d\n", 
               myinst->params.fid, myinst->params.qid, myinst->params.prio));
  } else {
    /* Error */
    if (result == MSR_CFY_RESULT_InUse) {
      MSR_DEBUG((MSR_DEBUG_PLUGIN | MSR_DEBUG_LEVEL_INFO,
              "testEM: Filter already installed!\n"));
    } else if (result == MSR_CFY_RESULT_Error) {
      MSR_DEBUG((MSR_DEBUG_PLUGIN | MSR_DEBUG_LEVEL_INFO,
              "testEM: General allocation error\n"));
    } else {
      MSR_DEBUG((MSR_DEBUG_PLUGIN | MSR_DEBUG_LEVEL_INFO,
              "testEM: Unknown Error adding filter!\n"));
    }
  }

  return result;
}

void testEM_handle_packet(struct rp_instance *this, void *plist)
{
  HDRQ_t *hdrs = plist;
  msr_bufhdr_t *hdr;
  struct testEM_instance *myinst = (struct testEM_instance *)this;
  int result = 0;

  /* The packet is passed in the tailq, see sys/queue.h for more info */
  if ((hdr = TAILQ_FIRST(hdrs)) == NULL) {
    MSR_DEBUG((MSR_DEBUG_PLUGIN | MSR_DEBUG_LEVEL_ERROR,
          "testEM_handle_packet: Input Packet List does not contain a valid packet!\n"));
    return;
  }

  myinst->pkt_count++;

  // if we are stopped (! Running) then do nothing
  if (!testem_isrunning(myinst)) {
    return;
  }

  // If its not time to change the filtering state then do nothing
  if (!new_event(myinst)) {
    return;
  }

  /*
   * How this works:
   *
   *   If we made it this far than a a "new_event" occured meaning
   *   it is time to change our state.
   *
   * */
  if (!testem_isfiltered(myinst)) { // Add EM filter
    if (testem_mkfltr(myinst, hdr) == 0 && testem_addfltr(myinst) == MSR_CFY_RESULT_OK)
      testem_setfiltered(myinst);
    else {
      MSR_DEBUG((MSR_DEBUG_PLUGIN | MSR_DEBUG_LEVEL_ERROR,
            "testEM_handle_packet: Error creating fltr or adding filter\n"));
    }
  } else {
    testem_remfltr(myinst);
    // assume it is removed or wasn't there!
    testem_unsetfiltered(myinst);
  }

  return;
}

void testEM_free_instance(struct rp_instance *this) {
  if (this) {
    MSR_DEBUG((MSR_DEBUG_PLUGIN | MSR_DEBUG_LEVEL_INFO,
          "testEM_free_instance: Freeing instance id %d ((class id %d)\n",
          this->instanceid, this->rpclass->classid));
    MSR_PLUGIN_FREE(this, M_MSR);
  } else {
    MSR_DEBUG((MSR_DEBUG_PLUGIN | MSR_DEBUG_LEVEL_WARNING,
          "testEM_free_instance: Passing a NULL this pointer\n"));
  }

  return;
}

void testEM_bind_instance(struct rp_instance *this) {
  /*
   * this->rpclass->itype = 0 Monitor or 1 then active
   * */
  MSR_DEBUG((MSR_DEBUG_PLUGIN | MSR_DEBUG_LEVEL_INFO,
        "testEM_bind_instance: Binding instance id %d (class id %d, Type %d)n",
        this->instanceid, this->rpclass->classid, this->rpclass->itype));
}

void testEM_unbind_instance(struct rp_instance *this) {
  struct testEM_instance *myinst = (struct testEM_instance *)this;
  MSR_DEBUG((MSR_DEBUG_PLUGIN | MSR_DEBUG_LEVEL_INFO,
        "testEM_unbind_instance: Unbinding instance id %d (class id %d, Type %d)\n",
        this->instanceid, this->rpclass->classid, this->rpclass->itype));
  if (testem_isfiltered(myinst)) {
    MSR_DEBUG((MSR_DEBUG_PLUGIN | MSR_DEBUG_LEVEL_INFO,
        "testEM_unbind_instance: ... Removing EM filter.\n"));
    testem_remfltr(myinst);
    testem_unsetfiltered(myinst);
  } else {
    MSR_DEBUG((MSR_DEBUG_PLUGIN | MSR_DEBUG_LEVEL_INFO,
          "testEM_unbind_instance: No filter installed ...\n"));
  }
  testem_unsetrunning(myinst);
}


/* ----------- loadable kernel module support ----------- */


/* External kernel module entry point.
 *
 * This function must match the name of the .o file.
 * It is called each time the module is loaded or unloaded.
 * The stat information is not needed here, so we will 
 * leave it lkm_nofunc().
 */

/* XXX int or void return type? */

int testEM  (struct lkm_table *lkmtp, int cmd, int ver, struct kernel_plugin_fct_struct *fctPtr)
{
  if (kernel_plugin_fcts == NULL) {
    kernel_plugin_fcts = fctPtr;
    kernel_plugin_variables = fctPtr->pluginVariables;
  }

  MSR_DEBUG((MSR_DEBUG_PLUGIN | MSR_DEBUG_LEVEL_VERBOSE,
        "testEM: Entry function -- initialized plugin_fcts pointers\n"));

  MSR_PLUGIN_DISPATCH(lkmtp, cmd, ver, testEM_load, testEM_unload, PLUGIN_LKM_NOFUNC_FCT);
}

/* 
 * This function is called each time the module is loaded. 
 */

int testEM_load(struct lkm_table *lkmtp, int cmd) {
  int err;

  MSR_DEBUG((MSR_DEBUG_PLUGIN | MSR_DEBUG_LEVEL_VERBOSE,
        "testEM_load: Loading testEM ...\n"));

  if (PLUGIN_LKM_EXISTS_FCT(lkmtp)) {
    MSR_DEBUG((MSR_DEBUG_PLUGIN | MSR_DEBUG_LEVEL_INFO,
      "testEM_load: plugin already exists! Returing EEXIST\n"));
    return (EEXIST);
  }

  testEM_init_class();

  err = PLUGIN_PCU_REGISTER_CLASS_FCT(testEM_get_class());

  if (err != RP_OK) {
    MSR_DEBUG((MSR_DEBUG_PLUGIN | MSR_DEBUG_LEVEL_ERROR,
          "testEM_load: Error (err = %d) encountered registering class.\n", err));
    return -1;
  }

  return 0;

}

/* 
 * This function is called each time the module is unloaded. 
 */

int testEM_unload(struct lkm_table *lkmtp, int cmd) {
  /* remove all existing instances and then remove class */
  /* XXX add cleanup code here */
  struct rp_class *rpclass;
  u_int32_t cid;
  int err;

  rpclass = testEM_get_class();

  cid = rpclass->classid;

  MSR_DEBUG((MSR_DEBUG_PLUGIN | MSR_DEBUG_LEVEL_VERBOSE,
        "testEM_unload: my rpclass id = %d, Struct Address = 0x%08x\n",
        cid, (u_int32_t)rpclass));

  if (PLUGIN_PCU_FREE_ALL_INSTANCES_FCT(rpclass) != RP_OK) {
    MSR_DEBUG((MSR_DEBUG_PLUGIN | MSR_DEBUG_LEVEL_ERROR,
          "testEM_unload: Error freeing all instances for class %d\n", cid));
    return -1;
  }

  if (PLUGIN_PCU_DEREGISTER_CLASS_FCT(rpclass) != RP_OK) {
    MSR_DEBUG((MSR_DEBUG_PLUGIN | MSR_DEBUG_LEVEL_ERROR,
          "testEM_unload: Error deregistering class %d\n", cid));
  }


  MSR_DEBUG((MSR_DEBUG_PLUGIN | MSR_DEBUG_LEVEL_VERBOSE,
        "testEM_unload: Unloaded class %d\n", cid));

  return 0;
}

int
testEM_handle_msg(struct rp_instance *this, void *buf, u_int8_t flags, u_int8_t seq, u_int8_t *len)
{
  struct testEM_instance *myinst = (struct testEM_instance *)this;
  u_int32_t *vals = (u_int32_t *)buf;
  u_int32_t id   = (u_int32_t)ntohl(*vals); // Our instance ID
  u_int32_t cmd = (u_int32_t)ntohl(*(vals + 1)); // our command
  int32_t data = (u_int32_t)ntohl(*(vals + 2));

  /* We are not using the control flags, valid falgs are
   *   Invalid = 0
   *   CMD    = 0x01
   *   REPLY  = 0x02
   *   Error  = 0x04
   *   EOF    = 0x08
   *   Next   = 0x10
   *   Cancel = 0x20
   *   Retry  = 0x40
   *   UNdefined = 0x80
   * Of these we only have the potential for seeing Cmd, Cancel and Next.
   *   */

  /* The first 4 byte word is the Instance Number for us. */
  MSR_DEBUG((MSR_DEBUG_PLUGIN | MSR_DEBUG_LEVEL_VERBOSE,
         "testEM_handle_msg: Flags 0x%x, Len %d, Seq %d, Instance %ud\n",
         flags, *len, seq, this->instanceid));

  switch (cmd) {
    case TESTEM_CMD_SETN:
      MSR_DEBUG((MSR_DEBUG_PLUGIN | MSR_DEBUG_LEVEL_INFO,
            "testEM_handle_msg: Changing N from %d to %d\n", myinst->N, (int)data));
      myinst->N = (int)data;
      break;
    case TESTEM_CMD_SETQID:
      MSR_DEBUG((MSR_DEBUG_PLUGIN | MSR_DEBUG_LEVEL_INFO,
            "testEM_handle_msg: Changing QID from %d to %d\n", myinst->QID, (int)data));
      myinst->QID = (int)data;
      break;
    case TESTEM_CMD_SETPRIO:
      MSR_DEBUG((MSR_DEBUG_PLUGIN | MSR_DEBUG_LEVEL_INFO,
            "testEM_handle_msg: Changing Prio from %d to %d\n", myinst->PRIO, (int)data));
      myinst->PRIO = (int)data;
      break;
    case TESTEM_CMD_SETPN:
      MSR_DEBUG((MSR_DEBUG_PLUGIN | MSR_DEBUG_LEVEL_INFO,
            "testEM_handle_msg: Changing PN from %d to %d\n", myinst->PN, (int)data));
      myinst->PN = (int)data;
      break;
    case TESTEM_CMD_SETSP:
      MSR_DEBUG((MSR_DEBUG_PLUGIN | MSR_DEBUG_LEVEL_INFO,
            "testEM_handle_msg: Changing SP from %d to %d\n", myinst->SP, (int)data));
      myinst->SP = (int)data;
      break;
    case TESTEM_CMD_GETVALS:
      MSR_DEBUG((MSR_DEBUG_PLUGIN | MSR_DEBUG_LEVEL_INFO,
            "testEM_handle_msg: N = %d, QID = %d, Prio = %d, PN = %d, SP = %d, Flags = %d, PktCount = %u\n",
            myinst->N, myinst->QID, myinst->PRIO, myinst->PN, myinst->SP, myinst->flags, myinst->pkt_count));
      break;
    case TESTEM_CMD_START:
      MSR_DEBUG((MSR_DEBUG_PLUGIN | MSR_DEBUG_LEVEL_INFO,
            "testEM_handle_msg: Starting ... \n"));
      testem_setrunning(myinst);
      break;
    case TESTEM_CMD_STOP:
      MSR_DEBUG((MSR_DEBUG_PLUGIN | MSR_DEBUG_LEVEL_INFO,
            "testEM_handle_msg: Stopping ... \n"));
      if (testem_isfiltered(myinst)) {
        MSR_DEBUG((MSR_DEBUG_PLUGIN | MSR_DEBUG_LEVEL_INFO,
            "testEM_handle_msg: ... Removing EM filter.\n"));
        testem_remfltr(myinst);
        testem_unsetfiltered(myinst);
      } else {
        MSR_DEBUG((MSR_DEBUG_PLUGIN | MSR_DEBUG_LEVEL_INFO,
              "testEM_handle_msg: Stopping ...\n"));
      }
      testem_unsetrunning(myinst);
      break;
    default:
      MSR_DEBUG((MSR_DEBUG_PLUGIN | MSR_DEBUG_LEVEL_WARNING,
            "testEM_handle_msg: Unknown command %u\n", (unsigned int)cmd));
      break;
  }

  *vals++ = (u_int32_t)htonl(myinst->N);
  *vals++ = (u_int32_t)htonl(myinst->QID);
  *vals++ = (u_int32_t)htonl(myinst->PRIO);
  *vals++ = (u_int32_t)htonl(myinst->PN);
  *vals++ = (u_int32_t)htonl(myinst->SP);
  *vals++ = (u_int32_t)htonl(myinst->flags);
  *vals   = (u_int32_t)htonl(myinst->pkt_count);
  *len = 7 * sizeof(u_int32_t);

  return 0;
}
