/*
 * -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
 *
 *                          http://www.ntop.org
 *
 * Copyright (C) 2003 Dinesh G. Dutt <ddutt@cisco.com>
 * Copyright (C) 2003-10 Luca Deri <deri@ntop.org>
 *
 * -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include "ntop.h"

#ifdef ENABLE_FC
#include <stdarg.h>
#include <assert.h>
#include "fcUtils.h"

/* **************************************** */

int isFlogiAcc (FcAddress *fcAddress, uint8_t r_ctl, uint8_t type, uint8_t cmd)
{
  if (((fcAddress->domain == 0xFF) && (fcAddress->area == 0xFF) && (fcAddress->port == 0xFE)) && 
      ((r_ctl & 0xF0) == FC_RCTL_ELS) && (type == FC_TYPE_ELS) &&
      (cmd == FC_ELS_CMD_ACC)) {
    return (TRUE);
  }
  else {
    return (FALSE);
  }
}

/* **************************************** */

/* Routine to extract WWN from FLOGI frame */
int fillFcHostInfo (const u_char *bp, HostTraffic *srcHost)
{
  assert (bp != NULL);

  srcHost->fcCounters->fcRecvSize = ntohs ((u_short)bp[10] & 0xFFF);
  memcpy (srcHost->fcCounters->pWWN.str, &bp[20], LEN_WWN_ADDRESS);
  memcpy (srcHost->fcCounters->nWWN.str, &bp[28], LEN_WWN_ADDRESS);

  setResolvedName(srcHost, (char*)srcHost->fcCounters->pWWN.str,
		  FLAG_HOST_SYM_ADDR_TYPE_FC_WWN);

  return (0);
}

/* **************************************** */

/* Routine to extract WWN from PLOGI frame */
int isPlogi (uint8_t r_ctl, uint8_t type, uint8_t cmd)
{
  if (((r_ctl & 0xF0) == FC_RCTL_ELS) && (type == FC_TYPE_ELS) &&
      (cmd == FC_ELS_CMD_PLOGI)) {
    return (TRUE);
  }
  else {
    return (FALSE);
  }
}

/* **************************************** */

int isLogout (uint8_t r_ctl, uint8_t type, uint8_t cmd)
{
  if (((r_ctl & 0xF0) == FC_RCTL_ELS) && (type == FC_TYPE_ELS) &&
      (cmd == FC_ELS_CMD_LOGO)) {
    return (TRUE);
  }
  else {
    return (FALSE);
  }
}

/* **************************************** */

int isRscn (uint8_t r_ctl, uint8_t type, uint8_t cmd)
{
  if (((r_ctl & 0xF0) == FC_RCTL_ELS) && (type == FC_TYPE_ELS) &&
      (cmd == FC_ELS_CMD_RSCN)) {
    return (TRUE);
  }
  else {
    return (FALSE);
  }
}

/* **************************************** */

HostTraffic *allocFcScsiCounters(HostTraffic *host) {
  if(host->fcCounters == NULL) {
    host->fcCounters = malloc(sizeof(FcScsiCounters));
    if(host->fcCounters == NULL) 
      return(NULL); /* Out of memory */
    
    memset(host->fcCounters, 0, sizeof(FcScsiCounters));
    host->fcCounters->vsanId = -1;
  }

  return(host);
}

/* **************************************** */

int fillFcpInfo (const u_char *bp, HostTraffic *srcHost, HostTraffic *dstHost)
{
  int offset = 0;
  uint32_t fcpDl;
    
  assert (bp != NULL);
  assert (srcHost != NULL);
  assert (dstHost != NULL);
    
  if (bp[offset] == 0) {
    /* This is a single-level LUN */
  }

  fcpDl = ntohl (*(uint32_t *)&bp[offset+28]);

  if(allocFcScsiCounters(srcHost) == NULL) return(0);
  if(allocFcScsiCounters(dstHost) == NULL) return(0);

  if (bp[offset+11] & 0x1) {
    incrementTrafficCounter(&srcHost->fcCounters->scsiWriteBytes, fcpDl);
    incrementTrafficCounter(&dstHost->fcCounters->scsiWriteBytes, fcpDl);
  }
  else if (bp[offset+11] & 0x2) {
    incrementTrafficCounter(&srcHost->fcCounters->scsiReadBytes, fcpDl);
    incrementTrafficCounter(&dstHost->fcCounters->scsiReadBytes, fcpDl);
  }

  return (0);
}

/* **************************************** */

FcFabricElementHash *getFcFabricElementHash (u_short vsanId, int actualDeviceId)
{
  FcFabricElementHash **theHash;
  uint myIdx = 0, idx;

  idx = vsanId % MAX_ELEMENT_HASH;
  theHash = myGlobals.device[actualDeviceId].vsanHash;    

  while(1) {
    if((theHash[idx] == NULL) || (theHash[idx]->vsanId == vsanId))
      break;

    idx = (idx+1) % MAX_ELEMENT_HASH;
    if(++myIdx == MAX_ELEMENT_HASH) {
      traceEvent(CONST_TRACE_WARNING, "updateElementHash(): hash full!");
      return (NULL);
    }
  }

  if(theHash[idx] == NULL) {
    theHash[idx] = (FcFabricElementHash*) calloc(1, sizeof(FcFabricElementHash));
    theHash[idx]->vsanId = vsanId;
  }

  return (theHash[idx]);
}

/* **************************************** */

int isValidFcNxPort (FcAddress *fcAddress)
{
  if (fcAddress != NULL) {
    if ((fcAddress->domain < MAX_FC_DOMAINS) && (fcAddress->domain))
      return (TRUE);
  }

  return (FALSE);
}

/* **************************************** */

int updateFcFabricElementHash (FcFabricElementHash **theHash, u_short vsanId,
                               const u_char *bp, FcAddress *srcAddr, FcAddress *dstAddr,
                               u_short protocol, u_char r_ctl, uint32_t pktlen)
{
  uint myIdx = 0, idx;
  FcFabricElementHash *hash;
  uint8_t cmd, srcDomain, dstDomain;
  u_short payload_len;
  uint8_t gs_type, gs_stype;

  idx = vsanId % MAX_ELEMENT_HASH;
    
  while(1) {
    if((theHash[idx] == NULL) || (theHash[idx]->vsanId == vsanId))
      break;

    idx = (idx+1) % MAX_ELEMENT_HASH;
    if(++myIdx == MAX_ELEMENT_HASH) {
      traceEvent(CONST_TRACE_WARNING, "updateElementHash(): hash full!");
      return (1);
    }
  }

  if(theHash[idx] == NULL) {
    theHash[idx] = (FcFabricElementHash*) calloc(1, sizeof(FcFabricElementHash));
    theHash[idx]->vsanId = vsanId;
  }

  /* ************************** */

  hash = theHash[idx];

  incrementTrafficCounter(&hash->totBytes, pktlen);
  incrementTrafficCounter(&hash->totPkts, 1);

#ifdef NOT_YET    
  if (protocol == FC_FTYPE_SWILS) {
    cmd = *bp;

    switch (cmd) {
    case FC_SWILS_ELP:
      incrementTrafficCounter(&hash->pmBytes, pktlen);
      incrementTrafficCounter(&hash->pmPkts, 1);
      hash->fabricConfStartTime = myGlobals.actTime;

      break;
    case FC_SWILS_ESC:
      incrementTrafficCounter(&hash->pmBytes, pktlen);
      incrementTrafficCounter(&hash->pmPkts, 1);
            
      break;
    case FC_SWILS_BF:
      incrementTrafficCounter(&hash->dmBytes, pktlen);
      incrementTrafficCounter(&hash->dmPkts, 1);
      incrementTrafficCounter(&hash->numBF, 1);
      hash->fabricConfStartTime = myGlobals.actTime;

      break;
    case FC_SWILS_RCF:
      incrementTrafficCounter(&hash->dmBytes, pktlen);
      incrementTrafficCounter(&hash->dmPkts, 1);
      incrementTrafficCounter(&hash->numRCF, 1);
      hash->fabricConfStartTime = myGlobals.actTime;
            
      break; 
    case FC_SWILS_EFP:
      incrementTrafficCounter(&hash->dmBytes, pktlen);
      incrementTrafficCounter(&hash->dmPkts, 1);

      /* Copy the latest EFP for the domain list */
      payload_len = ntohs (*(u_short *)&bp[2]);
      memcpy (hash->principalSwitch.str, &bp[8], sizeof (wwn_t));

      payload_len -= 16;
      payload_len = (payload_len > pktlen) ? pktlen : payload_len;
            
      hash->domainList = (FcDomainList *)malloc (payload_len);
      memcpy (hash->domainList, &bp[16], payload_len);

      hash->domainListLen = payload_len;

      break;
    case FC_SWILS_DIA:
      incrementTrafficCounter(&hash->dmBytes, pktlen);
      incrementTrafficCounter(&hash->dmPkts, 1);

      break;
    case FC_SWILS_RDI:
      incrementTrafficCounter(&hash->dmBytes, pktlen);
      incrementTrafficCounter(&hash->dmPkts, 1);
            
      break;
    case FC_SWILS_HLO:
      incrementTrafficCounter(&hash->fspfBytes, pktlen);
      incrementTrafficCounter(&hash->fspfPkts, 1);
      incrementTrafficCounter(&hash->hloPkts, 1);
            
      break;
    case FC_SWILS_LSU:
      incrementTrafficCounter(&hash->fspfBytes, pktlen);
      incrementTrafficCounter(&hash->fspfPkts, 1);
      incrementTrafficCounter(&hash->lsuBytes, pktlen);
      incrementTrafficCounter(&hash->lsuPkts, 1);
            
      break;
    case FC_SWILS_LSA:
      incrementTrafficCounter(&hash->fspfBytes, pktlen);
      incrementTrafficCounter(&hash->fspfPkts, 1);
      incrementTrafficCounter(&hash->lsaBytes, pktlen);
      incrementTrafficCounter(&hash->lsaPkts, 1);

      /* Check if fabric configuration is over */
      if (hash->fabricConfInProgress) {
	flags = bp[23];

	if (flags & 0x2) {
	  /* Compute duration of fabric configuration, avg */
	  confTime = difftime (myGlobals.actTime, hash->fabricConfStartTime);

	  if (hash->maxTimeFabricConf < confTime) {
	    hash->maxTimeFabricConf = confTime;
	  }

	  if (hash->minTimeFabricConf > confTime) {
	    hash->minTimeFabricConf = confTime;
	  }
	}
      }
            
      break;
    case FC_SWILS_RSCN:
      incrementTrafficCounter(&hash->rscnBytes, pktlen);
      incrementTrafficCounter(&hash->rscnPkts, 1);

      break;
    case FC_SWILS_DRLIR:
      break;
    case FC_SWILS_DSCN:
      break;
    case FC_SWILS_LOOPD:
      break;
            
    case FC_SWILS_MR:
      incrementTrafficCounter(&hash->zsBytes, pktlen);
      incrementTrafficCounter(&hash->zsPkts, 1);
            
      break;
    case FC_SWILS_ACA:
      incrementTrafficCounter(&hash->zsBytes, pktlen);
      incrementTrafficCounter(&hash->zsPkts, 1);

      hash->zoneConfStartTime = myGlobals.actTime;
      hash->zoneConfInProgress = TRUE;
            
      break;
    case FC_SWILS_RCA:
      incrementTrafficCounter(&hash->zsBytes, pktlen);
      incrementTrafficCounter(&hash->zsPkts, 1);

      /* We should in reality do this when we see the ACC for RCA */
      if (hash->zoneConfInProgress) {
	hash->zoneConfInProgress = FALSE;

	confTime = difftime (myGlobals.actTime, hash->zoneConfStartTime);
	if (hash->maxTimeZoneConf < confTime) {
	  hash->maxTimeZoneConf = confTime;
	}
                
	if (hash->minTimeZoneConf > confTime) {
	  hash->minTimeZoneConf = confTime;
	}
      }
      break;
    case FC_SWILS_SFC:
      incrementTrafficCounter(&hash->zsBytes, pktlen);
      incrementTrafficCounter(&hash->zsPkts, 1);
      incrementTrafficCounter(&hash->numZoneConf, 1);
            
      break;
    case FC_SWILS_UFC:
      incrementTrafficCounter(&hash->zsBytes, pktlen);
      incrementTrafficCounter(&hash->zsPkts, 1);
            
      break;
    case FC_SWILS_SWACC:
    case FC_SWILS_SWRJT:
      break;
    default:
      traceEvent (CONST_TRACE_ALWAYSDISPLAY, "updateFcFabricElementHash: Unknown SW_ILS command %d\n",
		  cmd);
      break;
    }
  }
  else {
  }
#else        
  if (protocol == FC_FTYPE_SWILS) {
    cmd = *bp;

    switch (cmd) {
    case FC_SWILS_ELP:
    case FC_SWILS_BF:
    case FC_SWILS_RCF:
      hash->fabricConfStartTime = myGlobals.actTime;
      break;
    case FC_SWILS_EFP:
      /* Copy the latest EFP for the domain list */
      payload_len = ntohs (*(u_short *)&bp[2]);
      memcpy (hash->principalSwitch.str, &bp[8], sizeof (wwn_t));

      payload_len -= 16;
      payload_len = (payload_len > pktlen) ? pktlen : payload_len;

      if (hash->domainList != NULL) {
	free (hash->domainList);
	hash->domainList = NULL;
      }
      hash->domainList = (FcDomainList *)malloc (payload_len);
      memcpy (hash->domainList, &bp[16], payload_len);

      hash->domainListLen = payload_len;

      break;
    case FC_SWILS_LSA:
      break;
            
    case FC_SWILS_ACA:
      hash->zoneConfStartTime = myGlobals.actTime;
      break;

    case FC_SWILS_RCA:
      break;
    }
  }
#endif
    
  /* Update Domain Stats */
  srcDomain = srcAddr->domain;
    
  if (srcDomain == FC_ID_SYSTEM_DOMAIN) {
    if (srcAddr->area == FC_ID_DOMCTLR_AREA) {
      srcDomain = srcAddr->port; /* This for addr of type FF.FC.<dom> */ 
    }
  }

  dstDomain = dstAddr->domain;
  if (dstDomain == FC_ID_SYSTEM_DOMAIN) {
    if (dstAddr->area == FC_ID_DOMCTLR_AREA) {
      dstDomain = dstAddr->port; /* This for addr of type FF.FC.<dom> */ 
    }
  }

  if (srcDomain != FC_ID_SYSTEM_DOMAIN) {
    incrementTrafficCounter(&hash->domainStats[srcDomain].sentBytes, pktlen);

    switch (protocol) {
    }
  }
    
  if (dstDomain != FC_ID_SYSTEM_DOMAIN) {
    incrementTrafficCounter(&hash->domainStats[dstDomain].rcvdBytes, pktlen);
        
    switch (protocol) {
    }
  }

  switch (protocol) {
  case FC_FTYPE_SWILS:
    incrementTrafficCounter(&hash->fcSwilsBytes, pktlen);
    break;
  case FC_FTYPE_SCSI:
    incrementTrafficCounter(&hash->fcFcpBytes, pktlen);
    break;
  case FC_FTYPE_SBCCS:
    incrementTrafficCounter(&hash->fcFiconBytes, pktlen);
    break;
  case FC_FTYPE_ELS:
    incrementTrafficCounter(&hash->fcElsBytes, pktlen);
    break;
  case FC_FTYPE_FCCT:
    gs_type = bp[4];
    gs_stype = bp[5];

    if ((gs_type == FCCT_GSTYPE_DIRSVC) && (gs_stype == FCCT_GSSUBTYPE_DNS)) {
      incrementTrafficCounter(&hash->fcDnsBytes, pktlen);
    }
    else {
      incrementTrafficCounter(&hash->otherFcBytes, pktlen);
    }
    break;
  case FC_FTYPE_IP:
    incrementTrafficCounter(&hash->fcIpfcBytes, pktlen);
    break;
        
  default:
    incrementTrafficCounter(&hash->otherFcBytes, pktlen);
    break;
  }

  return (0);
}

/* **************************************** */

void processFcNSCacheFile(char *filename) {
  char *token, *bufptr, *strtokState;
  FcNameServerCacheEntry *entry;
  HostTraffic *el;
  FcAddress fcid;
  uint32_t vsanId, domain, area, port, tgtType, i, j;
  wwn_t pWWN, nWWN;
  char alias[MAX_LEN_SYM_HOST_NAME];
  FILE *fd;
  int id, hashIdx = 0, entryFound, hex;
  char buffer[256];

  if (filename == NULL) {
    return;
  }

  if (myGlobals.fcnsCacheHash == NULL) {
    /* We cannot use the file if the entry is NULL */
    return;
  }
    
  if ((fd = fopen (filename, "r")) == NULL) {
    traceEvent (CONST_TRACE_WARNING, "Unable to open FC WWN cache file %s"
		"error = %d\n", filename, errno);
    return;
  }

  traceEvent (CONST_TRACE_ALWAYSDISPLAY, "Processing FC NS file %s\n", filename);
  while (!feof (fd) && (fgets(buffer, 256, fd) != NULL)) {
    alias[0] = '\0';
    pWWN.str[0] = '\0';
    nWWN.str[0] = '\0';
        
    /* Ignore lines that start with '#' as comments */
    if (strrchr(buffer, '#') != NULL) {
      continue;
    }

    /*
     * The file is a CSV list of lines with a line format as follows:
     * VSAN, FC_ID, pWWN, nWWN, Alias, Target type
     *
     * FC_ID is specified as a 3-byte hex i.e. 0xFFFFFD
     * pWWN & nWWN are specified as octets separated by ':'
     * Alias is a comma-separated string of max 64 chars
     * Target type is a decimal returned by the INQUIRY command
     *
     * If a field is missing, it is represented by a null string i.e. two
     * consecutive commas.
     */
    id = 0;
    bufptr = buffer;
    token = strtok_r (buffer, ",", &strtokState);
    while (token != NULL) {
      if (token[0]  != '\0') {
	switch (id) {
	case FLAG_FC_NS_CASE_VSAN:
	  if (isxdigit (*token)) {
	    sscanf (token, "%d", &vsanId);
	  }
	  else {
	    /* Invalid input. Skip rest of line */
	    token = NULL;
	    continue;
	  }
	  break;
	case FLAG_FC_NS_CASE_FCID:
	  if (isxdigit (*token)) {
	    if (sscanf (token, "%02hx.%02hx.%02hx", 
			(short unsigned int *)&domain, 
			(short unsigned int *)&area, 
			(short unsigned int *)&port) == 3) {
	      fcid.domain = domain;
	      fcid.area = area;
	      fcid.port = port;
	    }
	    else {
	      /* Invalid input. Skip rest of line */
	      token = NULL;
	      continue;
	    }
	  }
	  else {
	    /* Invalid input. Skip rest of line */
	    token = NULL;
	    continue;
	  }
	  break;
	case FLAG_FC_NS_CASE_PWWN:
	  for (i = 0, j = 0; i < LEN_WWN_ADDRESS; i++) {
	    sscanf (&token[j], "%02x:", &hex);
	    pWWN.str[i] = (char)hex;
	    j += 3;
	  }
	  break;
	case FLAG_FC_NS_CASE_NWWN:
	  for (i = 0, j = 0; i < LEN_WWN_ADDRESS; i++) {
	    sscanf (&token[j], "%02x:", &hex);
	    nWWN.str[i] = (char)hex;
	    j += 3;
	  }
	  break;
	case FLAG_FC_NS_CASE_ALIAS:
	  sscanf (token, "%63s", alias);
	  break;
	case FLAG_FC_NS_CASE_TGTTYPE:
	  if (isxdigit (*token)) {
	    sscanf (token, "%d", &tgtType);
	  }
	  else {
	    /* Invalid input. Skip rest of line */
	    token = NULL;
	    continue;
	  }
	  break;
	default:
	  break;
	}
      }
      id++;

      token = strtok_r (NULL, ",", &strtokState);
    }

    /* Validate inputs */
    if (id < FLAG_FC_NS_CASE_NWWN) {
      continue;
    }

    /* Obtain hash index. We pass -1 for device ID since this file is
     * device-independent.
     */
    hashIdx = hashFcHost (&fcid, vsanId, &el, -1);
    entry = myGlobals.fcnsCacheHash[hashIdx];

    entryFound = 0;
    while (entry != NULL) {
      if (memcmp ((uint8_t *)&(entry->fcAddress), (uint8_t *)&fcid,
		  LEN_FC_ADDRESS) == 0) {
	entryFound = 1;
	break;
      }

      entry = entry->next;
    }

    if (!entryFound) {
      if ((entry = malloc (sizeof (FcNameServerCacheEntry))) == NULL) {
	traceEvent (CONST_TRACE_ERROR, "Unable to malloc entry for FcNameServerCache Entry\n");
	return;
      }

      memset (entry, 0, sizeof (FcNameServerCacheEntry));
      entry->hashIdx = hashIdx;
      entry->next = myGlobals.fcnsCacheHash[hashIdx];
      myGlobals.fcnsCacheHash[hashIdx] = entry;
    }

    entry->vsanId = vsanId;
    entry->fcAddress = fcid;
    memcpy (&entry->pWWN.str[0], &pWWN.str[0], LEN_WWN_ADDRESS);
    memcpy (&entry->nWWN.str[0], &nWWN.str[0], LEN_WWN_ADDRESS);
    strncpy (&entry->alias[0], alias, MAX_LEN_SYM_HOST_NAME);
    entry->alias[MAX_LEN_SYM_HOST_NAME-1] = '\0';
  }
}

#endif /* ENABLE_FC */
