/*
    NullModule.c, Copyright  (c) by Claudio Jeker,
    Swiss Federal Institute of Technology,
    Computer Engineering and Networks Laboratory.
 
    TOPSY -- A Teachable Operating System.
             Implementation of a tiny and simple
             micro kernel for teaching purposes.
 
    For further information, please visit http://www.tik.ee.ethz.ch/~topsy
 
    This software is provided under the terms of the GNU General Public
    Licence. A full copy of the GNU GPL is provided in the file COPYING
    found in the development root of Topsy.
*/
/*
 
    File:                  $Source: /home/ruf/cvs_Topsy/cvs/Topsy/Net/Modules/Distributor.c,v $
    Affiliation:           ETH Zuerich, TIK
    Version:               $Revision: 2922 $
    Last Date of Change:   $Date: 2007-03-19 17:48:52 +0100 (Mon, 19 Mär 2007) $  by: $Author: baderma $
 
*/
#include "Topsy.h"
#include "NetModulesNew.h"
#include "NetModulesDef.h"
#include "NetDebug.h"
#include "LWThreads.h"
#include "HashList.h"
#include "Syscall.h"
#include "Support.h"
#include "Distributor.h"

static NetModuleId netDistributorException(LWPool lw, unsigned short chainKey);

void netDistributorInit(NetModule this)
{
  netattrSet (this->attributes, NETATTR_DISTRIBUTOR_KEY, 0);
  return;
}


int netDistributorConfig(NetModule this, Message* msg)
{
  unsigned long len = msg->msg.netConf.ModuleConf.len;
  unsigned long i;
  NetAttr *a = msg->msg.netConf.ModuleConf.attrVec;
  int exitCode = 0;

  for ( i = 0; i < len ; i++)
  {
    switch ( a[i].key )
    {
      /* add here all known keys */
    case NETATTR_DISTRIBUTOR_KEY:
    case NETATTR_TYPE_ETHERNET:
    case NETATTR_TYPE_IPv4:
    case NETATTR_TYPE_IPv6:
    case NETATTR_TYPE_ARP:
    case NETATTR_TYPE_UDP:
    case NETATTR_TYPE_ICMP:
    case NETATTR_TYPE_ICMP6:
    case NETATTR_TYPE_TCP:
    case NETATTR_TYPE_RAWIP:
    case NETATTR_TYPE_UNKOWN:
      if ( msg->id == NET_CONF_MODULESET )
      {
        netdbgPrintf(NETDEBUG_MODULES, "%s configure: %s = %#x\n",
                     MODNAME(this), netattrGetName(a[i].key), a[i].data);
        /* set attributes */
        if (! netattrSet(this->attributes, a[i].key, a[i].data) )
        {
          netdbgPrintf(0,
                       "%s configure: netattrSet failed (%i = %#x)\n",
                       MODNAME(this), a[i].key, a[i].data);
          CONF_FAILEDPLUS(exitCode);
          return exitCode;
        }
        CONF_SUCCEDPLUS(exitCode);
      }
      else if ( msg->id == NET_CONF_MODULEGET )
      {
        if (!netattrGet(this->attributes, a[i].key, &(a[i].data)))
        {
          netdbgPrintf(0,
                       "%s configure: netattrGet failed (%i)\n",
                       MODNAME(this), a[i].key);
          CONF_FAILEDPLUS(exitCode);
          return exitCode;
        }
        CONF_SUCCEDPLUS(exitCode);
      }
      else
      {
        netdbgPrintf(0, "%s configure: AIIEEE, got hit by a bus\n",
                     MODNAME(this));
        CONF_FAILEDPLUS(exitCode);
        return exitCode;
      }
      break;
    default:
      netdbgPrintf(0, "%s configure: unknown key %i\n",
                   MODNAME(this), a[i].key);
      CONF_IGNOREPLUS(exitCode);
    }
  }
  return exitCode;
}

void netDistributorMain(LWPool lw, ThreadArg arg)
{
  NetModule		my = (NetModule) arg;
  NetBuf			data;
  NetAttrs		meta;
  NetModuleId		id;
  HashList		chainLookup;
  unsigned short	metaKey, dataKey, chainKey, lowKey, highKey;

  /* init the local chainLookupTable, this has to be local or we get
   * some problems with the chain loader */
  if ( (chainLookup = hashListNew() ) == NULL)
  {
    netdbgPrintf(0, "%s: Init failed (hashListNew)\n", MODNAME(my));
    lwExit(lw);
  }

  while (1)
  {
    netmodRecv(lw, &data, &meta);
    PACKSTART(my);

    /* first get the DISTRIBUTOR_KEY */
    if (! netattrGet(my->attributes, NETATTR_DISTRIBUTOR_KEY, &metaKey) ||
        metaKey == 0 )
    {
      PACKERROR(my);
      netmodRIP(lw, data, meta,
                "netDistributor: failed to get my configuration\n");
      continue;
    }
    netdbgPrintf(NETDEBUG_MODULES, "%s: DISTRIBUTOR_KEY = %i\n",
                 MODNAME(my), metaKey);

    if ( metaKey == NETATTR_SENDTO_THREAD )
    {
      /* we are leaving the PoolThread so we try to get the ThreadId */
      netdbgPrintf(NETDEBUG_MODULES, "%s: probably a thread\n",
                   MODNAME(my));
      if (! netattrGet(meta, NETATTR_SENDTO_LOOP, &dataKey) )
      {
        /* send to thread */
        if ( netattrGet(meta, NETATTR_SENDTO_THREADLOW, &lowKey) &&
             netattrGet(meta, NETATTR_SENDTO_THREADHIGH, &highKey) )
        {
          Message msg;
          ThreadId to = lowKey | (highKey << 16);

          msg.id = NET_QUEUE_DATA;
          msg.msg.netqMsg.netData = data;
          msg.msg.netqMsg.netMeta = meta;
          netdbgPrintf(NETDEBUG_MODULES, "%s: send to thread %i\n",
                       MODNAME(my), (int) to);
          if ( tmMsgSend(to, &msg) != TM_MSGSENDOK )
          {
            PACKERROR(my);
            netmodRIP(lw, data, meta,
                      "netDistributor: failed to send to driver\n");
            continue;
          }
          //-CJ					printf("OUT-Distributor: sent packet to thread %i\n", to);
          PACKDONE(my);
          netmodRIP(lw, NULL, NULL, NULL);
          continue;
        }
        PACKERROR(my);
        netmodRIP(lw, data, meta,
                  "netDistributor: driver thread id not specified\n");
        continue;
      }
      netdbgPrintf(NETDEBUG_MODULES, "%s: LOOPBACK\n",
                   MODNAME(my));
      /* else LOOPBACK to a defined chain */
      netattrUnset(meta, NETATTR_SENDTO_LOOP);
    }
    else
    {
      /* or lookup the packet type via the metaKey of the distributor */

      if (! netattrGet(meta, metaKey, &dataKey) )
      {
        printf("%s: lookup %s\n", MODNAME(my), netattrGetName(metaKey) );
        PACKERROR(my);
        netmodRIP(lw, data, meta,
                  "netDistributor: failed to get packet info\n");
        continue;
      }
      // printf("J: looking up dataKey: %s -> %s\n", netattrGetName(metaKey), netattrGetName(dataKey));
    }
    netdbgPrintf(NETDEBUG_MODULES, "%s: PACKET_TYPE = %i\n",
                 MODNAME(my), dataKey);

    /*        netdbgPrintf(NETDEBUG_MODULES, "M: Report: %x\n", my->attributes);
    netdbgPrintf(NETDEBUG_MODULES, "M: Report: %d\n", dataKey);
    netdbgPrintf(NETDEBUG_MODULES, "M: Report: %x\n", &chainKey);
    netdbgPrintf(NETDEBUG_MODULES, "M: Report: %x\n", NETATTR_TYPE_ICMP6);
    */

    /* now lookup the chain key */
    if (! netattrGet(my->attributes, dataKey, &chainKey) )
    {
      PACKERROR(my);
      netmodRIP(lw, data, meta,
                "netDistributor: failed to get chainkey (unknown protocol)\n");
      continue;
    }
    netdbgPrintf(NETDEBUG_MODULES, "%s: CHAINKEY = %#x\n",
                 MODNAME(my), chainKey);

    /* at last get the chain via the lookup table */
    hashListGet(chainLookup, (void **) &id, chainKey);
    /* if hashListGet fails (HASHNOTFOUND) id will be set to 0 */
    netdbgPrintf(NETDEBUG_MODULES, "%s: local CHAIN = %i\n",
                 MODNAME(my), id);

    if ( id == 0 )
    {
      /* chain is not loaded, try to load it */
      id = netDistributorException(lw, chainKey);
      if ( id != 0 &&
           hashListAdd(chainLookup, (void*) id, chainKey) != HASHOK )
      {
        PACKERROR(my);
        netmodRIP(lw, data, meta,
                  "netDistributor: failed to update chain lookuptable\n");
        continue;
      }
    }

    if ( id == 0 || id == -1 )
    {
      /* no chain found */
      PACKERROR(my);
      netmodRIP(lw, data, meta,
                "netDistributor: no chain found\n");
      continue;
    }
    /* got chain, send to the first module */
    PACKDONE(my);
    netmodSend(lw, id, data, meta);
  }
}


static NetModuleId netDistributorException(LWPool lw, unsigned short chainKey)
{
  Message msg;
  ThreadId loader = NET_MODULE_LOADER_THREAD;


  msg.id = NET_CONF_CHAINREQ;
  msg.msg.netConf.ChainReq.chainKey = chainKey;
  if (lwMsgSend(lw, loader, &msg) != LW_OK)
  {
    netdbgPrintf(NETDEBUG_MODULES,
                 "netDistributorException: lwMsgSend failed\n");
    return 0;
  }

  if (lwMsgRecv(lw,  &loader, 0, &msg, 0) != LW_OK)
  {
    netdbgPrintf(NETDEBUG_MODULES,
                 "netDistributorException: lwMsgRecv failed\n");
    return 0;
  }

  if (loader != NET_MODULE_LOADER_THREAD )
  {
    netdbgPrintf(NETDEBUG_MODULES,
                 "netDistributorException: bad reply message: wrong thread\n");
    return 0;
  }
  if (msg.id != NET_CONF_CHAINREPLY )
  {
    netdbgPrintf(NETDEBUG_MODULES,
                 "netDistributorException: bad reply message: wrong id:%d not:%d\n", msg.id, NET_CONF_CHAINREPLY);
    return 0;
  }

  return msg.msg.netConf.ChainReq.id;
}

/* now the information needed to start all distributors */
NetModuleId		distributorTable[32] = {
                                      0,
                                      OUT_DISTRIBUTOR_TYPE,
                                      OSI_23_DISTRIBUTOR_TYPE,
                                      OSI_34_DISTRIBUTOR_TYPE,
                                      OSI_43_DISTRIBUTOR_TYPE,
                                      OSI_32_DISTRIBUTOR_TYPE,
                                      OSI_34_DISTRIBUTOR_TYPE,
                                      OSI_32_DISTRIBUTOR_TYPE,
                                      OUT_DISTRIBUTOR_TYPE,
                                      OSI_32_DISTRIBUTOR_TYPE,
                                      OSI_34_DISTRIBUTOR_TYPE,
                                      OUT_DISTRIBUTOR_TYPE,
                                      OSI_23_DISTRIBUTOR_TYPE,
                                      OUT_DISTRIBUTOR_TYPE,
                                      OSI_43_DISTRIBUTOR_TYPE,
                                      OUT_DISTRIBUTOR_TYPE,
                                      /* rest is 0 initalized */
                                    };

NetModuleRegDesc distributorModuleRegistry[] = {
      {
        NULL, NULL,
        IN_DISTRIBUTOR_TYPE,
        0,
        netDistributorMain,
        netDistributorConfig,
        netDistributorInit,
        "Distributor IN"
      },
      {
        NULL, NULL,
        OUT_DISTRIBUTOR_TYPE,
        0,
        netDistributorMain,
        netDistributorConfig,
        netDistributorInit,
        "Distributor OUT"
      },
      {
        NULL, NULL,
        OSI_23_DISTRIBUTOR_TYPE,
        0,
        netDistributorMain,
        netDistributorConfig,
        netDistributorInit,
        "Distributor 2/3"
      },
      {
        NULL, NULL,
        OSI_34_DISTRIBUTOR_TYPE,
        0,
        netDistributorMain,
        netDistributorConfig,
        netDistributorInit,
        "Distributor 3/4"
      },
      {
        NULL, NULL,
        OSI_43_DISTRIBUTOR_TYPE,
        0,
        netDistributorMain,
        netDistributorConfig,
        netDistributorInit,
        "Distributor 4/3"
      },
      {
        NULL, NULL,
        OSI_32_DISTRIBUTOR_TYPE,
        0,
        netDistributorMain,
        netDistributorConfig,
        netDistributorInit,
        "Distributor 3/2"
      },
      {
        NULL, NULL,
        0,
        0,
        NULL,
        NULL,
        NULL,
        ""
      }
    };
