/****************************************************************
 ****************************************************************
 **                                                            **
 **    (C)Copyright 2005-2006, American Megatrends Inc.        **
 **                                                            **
 **            All Rights Reserved.                            **
 **                                                            **
 **        6145-F, Northbelt Parkway, Norcross,                **
 **                                                            **
 **        Georgia - 30071, USA. Phone-(770)-246-8600.         **
 **                                                            **
 ****************************************************************
 ****************************************************************/
/*****************************************************************
 *
 * LANIfc.c
 * LAN Interface Handler
 *
 * Author: Govind Kothandapani <govindk@ami.com>
 *       : Bakka Ravinder Reddy <bakkar@ami.com>
 *
 *****************************************************************/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/socket.h>

#include "com_IPMI_LANIfc.h"
#include "com_IPMI_RMCP.h"
#include "RMCP.h"
#include "LOOPBACK_ifc.h"
#include "com_IPMIDefs.h"
#include "RMCP.h"
#include "MD.h"
#include <netdb.h>        /* getaddrinfo(3) et al.                       */
#include <netinet/in.h>   /* sockaddr_in & sockaddr_in6 definition.      */
#include  <net/if.h>
#include <sys/prctl.h>
#include "main.h"
#include "Session.h"

#define NO_OF_RETRY                     3
#define MAX_POSSIBLE_IPMI_DATA_SIZE     1000
#define MAX_LAN_BUFFER_SIZE             1024
#define LAN_TIMER_INTERVAL              10

#define RMCP_CLASS_MSG_OFFSET           3
#define IPMI_MSG_AUTH_TYPE_OFFSET       4
#define RMCP_ASF_PING_MESSAGE_LENGTH    12
#define IPMI_MSG_LEN_OFFSET             13
#define IPMI20_MSG_LEN_OFFSET           14
#define RMCP_CLASS_MSG_ASF              0x06
#define RMCP_CLASS_MSG_IPMI             0x07

//static void*    LANTimer (void*);
static void*    RecvLANPkt (void*);
static int      SendLANPkt (MsgPkt_T *pRes );
static void     ProcessLANReq (  MsgPkt_T* pReq);
static void		ProcessBridgeMsg (  MsgPkt_T* pReq );

static int 		InitUDPSocket(void);
static int SetIPv4Header(int socketID);

int gSocket_lo = -1;
int gFd_LoIfcQ, gFd_LoResQ;

/**
 * @brief LAN Interface Task.
**/
void *LoIfcTask (void *param)
{
	MsgPkt_T    Req;
	int i,BMC,Buffer = 0;
	uint8_t ethindex=0;
	
	prctl(PR_SET_NAME,__FUNCTION__,0,0,0);
	int curThreadIndex = 0;

	/* Init LAN SMB */
	printf("LoIfc Task Started... \n");

	//create LAN_IFC_Q
    if(-1 != access(LO_IFC_Q, F_OK))
    {
        remove(LO_IFC_Q);
    }
    if(0 != mkfifo (LO_IFC_Q, 0777))
    {
        printf("%s: Create %s fifo failed! %s\n", __FUNCTION__, LO_IFC_Q, strerror(errno));
        return (void*)-1;
    }
    gFd_LoIfcQ = open (LO_IFC_Q, O_RDWR);
    if(-1 == gFd_LoIfcQ)
    {
        printf("%s: Open %s fifo failed! %s\n", __FUNCTION__, LO_IFC_Q, strerror(errno));
        return (void*)-1;
    }

	//create LAN_RES_Q
    if(-1 != access(LO_RES_Q, F_OK))
    {
        remove(LO_RES_Q);
    }
    if(0 != mkfifo (LO_RES_Q, 0777))
    {
        printf("%s: Create %s fifo failed! %s\n", __FUNCTION__, LO_RES_Q, strerror(errno));
        return (void*)-1;
    }
    gFd_LoResQ = open (LO_RES_Q, O_RDWR);
    if(-1 == gFd_LoResQ)
    {
        printf("%s: Open %s fifo failed! %s\n", __FUNCTION__, LO_RES_Q, strerror(errno));
        return (void*)-1;
    }

    /* Create LAN socket */
    if(0 != Lo_InitUDPSocket())
    {
    	printf("%s: Create UDP socket failed! %s\n", __FUNCTION__, strerror(errno));
    	return (void *)-1; 
    }

	/*Create a thread to recv UDS Pkt */
	gThreadIndex++;	
    if(0 != pthread_create(&gThreadIDs[gThreadIndex],NULL,RecvLoPkt,NULL))
    {
        printf("%s: Create RecvLANPkt thread failed! %s\n", __FUNCTION__, strerror(errno));
        return (void *)-1; 
    }

	while (1)
	{		
		/* Wait for a message in LANIfc interface Queue */
		if (0 != GetMsg (gFd_LoIfcQ, &Req, WAIT_INFINITE))
		{
			printf("LANIfc.c : Error fetching message from hLANIfc_Q\n");
			continue;
		}

		switch (Req.Param)
		{
		case LAN_SMB_REQUEST :
			ProcessLANReq (&Req);
			break;

		case BRIDGING_REQUEST :
			ProcessBridgeMsg (&Req );
			break;

		default :
			printf ("LOOPBACK_Ifc.c : Invalid request\n");
			break;
		}
	}
}

/**
 * @fn InitUnixDomainSocket
 * @brief This function is used to create the Socket for each BMC
 * @param BMCInst
**/
static int Lo_InitUDPSocket(void)
{
  	struct  sockaddr_in local;
  	char    UDPIfcName [MAX_STR_LENGTH];
  	int 	reuseaddr = 1;

  	memset(UDPIfcName,0,sizeof(UDPIfcName));

  	if(gSocket_Lo != -1)
  	{
  		close(gSocket_Lo);
  		gSocket_Lo = -1;
  	}

	memset(&local, 0, sizeof(struct sockaddr_in));
	local.sin_family = AF_INET;
	local.sin_port = htons(623);
	local.sin_addr.s_addr = htonl(INADDR_ANY);  

    gSocket_LAN = socket(AF_INET,SOCK_DGRAM,0);
    if(-1 == gSocket_LAN)
    {
        printf("LANIfc.c : Unable to create the UDP Socket\n");
        return -1;
    }

    //printf("UDP Socket %d\n", gSocket_LAN);

    strcpy(UDPIfcName,"eth0");
	
	if (0 != setsockopt (gSocket_LAN, SOL_SOCKET,SO_BINDTODEVICE, UDPIfcName, sizeof (UDPIfcName)+1))
	{
		printf("LANIfc.c: SetSockOpt(SO_BINDTODEVICE) Failed for UDP Socket\n");
		return -1;
	}
	if (0 != setsockopt(gSocket_LAN, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(int)))
	{
		printf("LANIfc.c: Setsockopt(SO_REUSEADDR) Failed for UDP socket\n");
	}
	SetIPv4Header(gSocket_LAN);
  
    /* Bind */
    if (-1 == bind(gSocket_LAN,(struct sockaddr *)&local,sizeof(local)))
    {
        printf("LANIfc.c : Error binding  UDP Socket, %d, %s\n", errno, strerror(errno));
        return -1;
    }

    //printf("Create UDP socket successfully, socket %d, port: %d\n", gSocket_LAN, local.sin_port);

    return 0;
}

/**
 * @fn  ReadData
 * @brief This function receives the IPMI LAN request packets
 *
 * @param pMsgPkt - pointer to message packet
 * @param Socket  - Socket handle
**/
static
int  ReadData (MsgPkt_T *pMsgPkt, int Socket )
{
	unsigned int    SourceLen = 0,Index,SocktIndex=0,addrlen=0;
	int 			Channel = 0;
	uint8_t   *pData      = pMsgPkt->Data;
	int16_t   Len         = 0;
	uint16_t  RecvdLen    = 0;
	uint16_t  IPMIMsgLen  = 0;
	uint16_t  IPMIMsgOffset   = 0;
	struct  sockaddr_in Sourcev4;
	struct  sockaddr_in server_v4addr;
	void *Source = NULL ;
	void *server_addr = NULL ;
	uint8_t  WaitCount;
	int i;
			
	
	Source = &Sourcev4;
	SourceLen = sizeof (Sourcev4);
	
	//printf("ReadData:\n");
	 //SourceLen = sizeof (Source);
	/* Read minimum bytes to find class of message */
	while (RecvdLen < RMCP_CLASS_MSG_OFFSET)
	{
		Len = recvfrom (Socket, &pData[RecvdLen], MAX_LAN_BUFFER_SIZE, 0, (struct sockaddr *)Source, &SourceLen);
		// printf("Recv1 %d: ", Len);
		// for(i=0;i<Len;i++)
		// 	printf("%#x ", pData[RecvdLen+i]);
		// printf("\n");

		if ((Len >= -1) && (Len <= 0))
		{
			return -1;
		}
		RecvdLen += (uint16_t)Len;
	}


	/*  if RMCP Presence Ping Requested */
	if (RMCP_CLASS_MSG_ASF == pData[RMCP_CLASS_MSG_OFFSET])
	{
		/* Read remaining RMCP ASF ping message */
		while (RecvdLen < RMCP_ASF_PING_MESSAGE_LENGTH)
		{
			Len = recvfrom (Socket, &pData[RecvdLen], MAX_LAN_BUFFER_SIZE, 0, (struct sockaddr *)Source, &SourceLen);
			// printf("Recv2 %d: ", Len);
			// for(i=0;i<Len;i++)
			// 	printf("%#x ", pData[RecvdLen+i]);
			// printf("\n");
			if ((Len >= -1) && (Len <= 0))
			{
				return -1;
			}
			RecvdLen += (uint16_t)Len;
		}
	}
			/*else if IPMI RMCP request */
	else if (RMCP_CLASS_MSG_IPMI == pData[RMCP_CLASS_MSG_OFFSET])
	{
		/* Read minimum no of bytes for IPMI Auth type offset*/
		while (RecvdLen < IPMI_MSG_AUTH_TYPE_OFFSET)
		{
			Len = recvfrom (Socket, &pData[RecvdLen], MAX_LAN_BUFFER_SIZE, 0,(struct sockaddr *)Source, &SourceLen);
			// printf("Recv3 %d: ", Len);
			// for(i=0;i<Len;i++)
			// 	printf("%#x ", pData[RecvdLen+i]);
			// printf("\n");
			if ((Len >= -1) && (Len <= 0))
			{
				return -1;
			}
			RecvdLen += (uint16_t)Len;
		}
		/* Get the IPMI message length offset based on format/authentication type */
		if (pData [IPMI_MSG_AUTH_TYPE_OFFSET] == RMCP_PLUS_FORMAT)
		{
			IPMIMsgOffset = IPMI20_MSG_LEN_OFFSET + 1;
		}
		else if (pData [IPMI_MSG_AUTH_TYPE_OFFSET] == AUTH_TYPE_NONE)
		{
			IPMIMsgOffset = IPMI_MSG_LEN_OFFSET;
		}
		else
		{
			IPMIMsgOffset = IPMI_MSG_LEN_OFFSET + AUTH_CODE_LEN;
		}

		/* Read minimum no of bytes for IPMI message length offset*/
		while (RecvdLen < IPMIMsgOffset)
		{
			Len = recvfrom (Socket, &pData[RecvdLen], MAX_LAN_BUFFER_SIZE, 0,(struct sockaddr *)Source, &SourceLen);
			// printf("Recv4 %d: ", Len);
			// for(i=0;i<Len;i++)
			// 	printf("%#x ", pData[RecvdLen+i]);
			// printf("\n");
			if ((Len >= -1) && (Len <= 0))
			{
				return -1;
			}
			RecvdLen += (uint16_t)Len;
		}

		/* Get the IPMI message length based on RMCP format type */
		if (pData [IPMI_MSG_AUTH_TYPE_OFFSET] == RMCP_PLUS_FORMAT)
		{
			IPMIMsgLen = *((uint16_t*)&pData [IPMI20_MSG_LEN_OFFSET]);
		}
		else
		{
			IPMIMsgLen = pData [IPMIMsgOffset];
		}
		/* We are assuming that we cannot get more than 17 K data in IPMI Msg */
		/* This work around for fix the malformed IPMI Msg length */

		if(IPMIMsgOffset > MAX_POSSIBLE_IPMI_DATA_SIZE )
		{
			return -1;
		}
		/* Read the remaining IPMI message packets */
		WaitCount = 3;
		while (RecvdLen < IPMIMsgLen)
		{
			Len = recvfrom (Socket, &pData[RecvdLen], MAX_LAN_BUFFER_SIZE, 0,(struct sockaddr *)Source, &SourceLen);
			// printf("Recv5 %d: ", Len);
			// for(i=0;i<Len;i++)
			// 	printf("%#x ", pData[RecvdLen+i]);
			// printf("\n");
			if ((Len >= -1) && (Len <= 0))
			{
				if(Len == -1)
				{
					if(errno == EAGAIN)
					{
					   WaitCount--;
					}
					else
					   return -1;
				}
				else
					return -1;
			}
			if(WaitCount == 0)
			{
				return -1;
			}
			RecvdLen += (uint16_t)Len;
		}
	}/* else other RMCP class are not supported. */
	else
	{
		printf ("Unknown RMCP class\n");
	}

	pMsgPkt->Size     = RecvdLen;
	pMsgPkt->UDPPort  = ((struct sockaddr_in *)Source)->sin_port;
	pMsgPkt->Socket   = Socket;
	pMsgPkt->Channel  = LAN_RMCP_CHANNEL;
	
	// printf("\nLan received %d: ",RecvdLen);
	// for(i=0;i<RecvdLen;i++)
	// {
	// 	printf("%#x ", pData[i]);
	// }
	// printf("\n");

	if(SourceLen!=0) 
	{
		memcpy (pMsgPkt->IPAddr, &((struct sockaddr_in *)Source)->sin_addr.s_addr, sizeof (struct in_addr));
	}

	pMsgPkt->Param    = LAN_SMB_REQUEST;
	
	return 0;
}

void *RecvLANPkt(void *pArg)
{
    MsgPkt_T            MsgPkt;
    struct timeval Timeout;
    struct sockaddr_in local;
    int RetVal,max,Index = 0;
    unsigned int locallen;
    prctl(PR_SET_NAME,__FUNCTION__,0,0,0);
    fd_set  fds;

    memset(&local,0,sizeof(local));

    locallen=sizeof(local);

    printf("RecvLANPkt start...\n");
    while(1)
    {
        Timeout.tv_sec  =   SESSION_TIMEOUT; 
        Timeout.tv_usec  =   0; 

        FD_ZERO(&fds);
        if(gSocket_LAN != -1)
            FD_SET(gSocket_LAN,&fds);
        
        max = gSocket_LAN+1;

        /*Waits for an event to occur on socket*/
        RetVal = select (max, &fds, NULL, NULL, &Timeout);
        if (-1 == RetVal)
        {
            continue;
        }
        if (0 == RetVal)
        {
            /* Its due to timeout - continue */
            continue;
        }
        
        /*Accepting Connection*/
        if(FD_ISSET(gSocket_LAN,&fds))
        {
            if(0 ==  ReadData( &MsgPkt,gSocket_LAN))
            {
                /* Post the request packet to LAN Interface Queue */
				if (0 != PostMsg (gFd_LanIfcQ, &MsgPkt))
				{
					printf ("Warning: LANIfc.c Error posting message to LANIfc Q\n");
				}               
            }       
            else
            {
                close(gSocket_LAN);
                gSocket_LAN = -1;
            }      
        }
   }
}

/**
 * @fn SendLANPkt
 * @brief This function sends the IPMI LAN Response to the requester
 * @param pRes - Response message.
**/
int SendLANPkt (MsgPkt_T *pRes )
{
	struct  sockaddr_in Dest;
	//struct stat Stat;
	int ret = 0;
	
	/* Set the destination UDP port and IP Address */
	Dest.sin_family     =   AF_INET;
	Dest.sin_port       =   pRes->UDPPort;
	memcpy (&Dest.sin_addr.s_addr, pRes->IPAddr, sizeof (struct in_addr));
	
	/* Send the LAN response packet */
	//int i;
	//printf("\nLan send socket: %d, byte: %d: ", pRes->Socket, pRes->Size);
	// for (i = 0; i < pRes->Size; ++i)
	// {
	// 	printf("%#x ", pRes->Data[i]);
	// }
	//printf("\n");

	//Check the socket before send a message on a socket
	//if (fstat(pRes->Socket, &Stat) != -1) 
	{
		
		ret = sendto (pRes->Socket, pRes->Data, pRes->Size, 0, (struct sockaddr*)&Dest, sizeof (Dest));
		if (ret == -1)
		{
			printf ("Warning: LANIfc.c Error sending response packets to LAN, %s\n",strerror(errno));
		}
		// else
		// {
		// 	printf ("LANIfc.c : LAN packet sent successfully\n");
		// }
	}

	return 1;
}


/**
 * @brief Process SMB Request.
 * @param pReq - Request message.
**/
static void
ProcessLANReq (  MsgPkt_T* pReq )
{
	MsgPkt_T        Res;
	SessionInfo_T*  pSessionInfo;
	SessionHdr_T*   pSessionHdr;
	SessionHdr2_T*  pSessionHdr2;
	uint32_t          SessionID =0;

	/* Copy the request to response */
	Res = *pReq;

	/* Save the LAN header inofmation */
	pSessionHdr  = (SessionHdr_T*) (((RMCPHdr_T*)pReq->Data) + 1);
	pSessionHdr2 = (SessionHdr2_T*)(((RMCPHdr_T*)pReq->Data) + 1);
	if (RMCP_PLUS_FORMAT == pSessionHdr->AuthType)
	{
		SessionID = pSessionHdr2->SessionID;
		pSessionInfo = getSessionInfo (SESSION_ID_INFO, &SessionID);
	}
	else
	{
		SessionID = pSessionHdr->SessionID;
		pSessionInfo = getSessionInfo (SESSION_ID_INFO, &SessionID );
	}
	if (NULL != pSessionInfo)
	{
		pSessionInfo->LANRMCPPkt.UDPHdr.SrcPort = Res.UDPPort;
		pSessionInfo->hSocket                   = Res.Socket;
		//SocktIndex=GetSocketInfoIndex(pSessionInfo->hSocket );
		
		memcpy (pSessionInfo->LANRMCPPkt.IPHdr.Srcv4Addr, Res.IPAddr, sizeof (struct in_addr));
		memcpy (&pSessionInfo->LANRMCPPkt.RMCPHdr, Res.Data, sizeof (RMCPHdr_T));
	}

	/* Process the RMCP Request */
	Res.Size = ProcessRMCPReq ((RMCPHdr_T*)pReq->Data, (RMCPHdr_T*)Res.Data);

	/* ResLen is 0, don't send the packet */
	if (0 == Res.Size )
	{
        printf ("Note: LANIfc.c LAN request packet dropped, not processed\n");
	 	return;
	}

	/* Sent the response packet */
	SendLANPkt (&Res);

	return;
}

/*--------------------------------------------
 * ProcessBridgeMsg
 *--------------------------------------------*/
static void
ProcessBridgeMsg (  MsgPkt_T* pReq )
{
	MsgPkt_T        ResPkt;
	uint16_t          PayLoadLen   = 0;
	uint8_t           PayLoadType  = 0;
	SessionInfo_T   *pSessionInfo = getSessionInfo (SESSION_HANDLE_INFO, pReq->Data);

 	//printf ("LANIfc: Bridge Request\n");

	if (NULL == pSessionInfo)
	{
		printf ("Warning: LANIfc ProcessBridgeMsg - No Session with the LAN\n");
		return;
	}

	/* Copy Lan RMCP headers from Session Record */
	ResPkt.UDPPort  = pSessionInfo->LANRMCPPkt.UDPHdr.SrcPort;
	ResPkt.Socket   = pSessionInfo->hSocket;
	
	memcpy (ResPkt.IPAddr, pSessionInfo->LANRMCPPkt.IPHdr.Srcv4Addr, sizeof (struct in_addr));
	memcpy (ResPkt.Data, &pSessionInfo->LANRMCPPkt.RMCPHdr, sizeof (RMCPHdr_T));

// #if IPMI20_SUPPORT == 1
// 	if (RMCP_PLUS_FORMAT == pSessionInfo->AuthType)
// 	{
// 		/* Fill Session Header */
// 		pSessionInfo->OutboundSeq++;
// 		PayLoadLen      = pReq->Size - 1;
// 		PayLoadType     = pReq->Cmd;
// 		PayLoadType    |= (pSessionInfo->SessPyldInfo[PayLoadType].AuxConfig[0] & 0xC0);
// 		PayLoadLen      = Frame20Payload (PayLoadType, (  RMCPHdr_T*)&ResPkt.Data [0],
// 									 &pReq->Data[1], PayLoadLen, pSessionInfo );
// 	}
// 	else
// #endif /*IPMI20_SUPPORT == 1*/
	{
		/* Fill Session Header */
		  SessionHdr_T* pSessionHdr = (  SessionHdr_T*)(&ResPkt.Data [sizeof(RMCPHdr_T)]);
		  uint8_t*        pPayLoad    = (  uint8_t*)(pSessionHdr + 1);

		pSessionHdr->AuthType       = pSessionInfo->AuthType;
		pSessionHdr->SessionSeqNum  = pSessionInfo->OutboundSeq++;
		pSessionHdr->SessionID      = pSessionInfo->SessionID;

		/* If AuthType is not 0 - Compute AuthCode */
		if (0 != pSessionInfo->AuthType)
		{
			PayLoadLen = AUTH_CODE_LEN;
			pPayLoad [PayLoadLen++] = pReq->Size - 1;
			memcpy (&pPayLoad [PayLoadLen], (pReq->Data + 1), (pReq->Size - 1));
			PayLoadLen += pReq->Size;
			PayLoadLen--;
			PayLoadLen += sizeof (SessionHdr_T) + sizeof (RMCPHdr_T);
			ComputeAuthCode (pSessionInfo->Password, pSessionHdr,
							 (  IPMIMsgHdr_T*) &pPayLoad [AUTH_CODE_LEN+1],
							 pPayLoad, MULTI_SESSION_CHANNEL);
		}
		else
		{
		   pPayLoad [PayLoadLen++] = pReq->Size - 1;
			/*  Fill the ipmi message */
		   memcpy (&pPayLoad [PayLoadLen], (pReq->Data + 1), (pReq->Size - 1));
		   PayLoadLen += pReq->Size;
		   PayLoadLen--;
		   PayLoadLen += sizeof (SessionHdr_T) + sizeof (RMCPHdr_T);
		}

	}

	ResPkt.Size = PayLoadLen;

	if(pSessionInfo->Activated)
	{
		/* Sent the response packet */
		SendLANPkt (&ResPkt);
	}

	return;
}


// /**
//  * @fn  LANTimer
//  * @brief This function handles the time out for lan connections.
//  * @param None
// **/
// static
// void*  LANTimer (void *pArg)
// {
// 	int *inst = (int*)pArg;
// 	int BMCInst= *inst;
// 	prctl(PR_SET_NAME,__FUNCTION__,0,0,0);

// 	while (1)
// 	{
// 		UpdateTimeout (BMCInst);
// 		sleep (LAN_TIMER_INTERVAL);
// 	}

// 	return 0;
// }


static int SetIPv4Header(int socketID)
{
	uint8_t   	flag = 0;
	uint8_t 	TimeToLive = 64;
	//uint8_t		TypeOfService = 0;

	if (setsockopt(socketID, IPPROTO_IP, IP_TTL,
			&TimeToLive, sizeof(uint8_t)) == -1)
	{
		printf("LANIfc.c: Setsockopt(IP_TTL) Failed for UDP socket:");
		return -1;
	}

	flag = 0;	/* Never send DF frames.  */
	if (setsockopt(socketID, IPPROTO_IP, IP_MTU_DISCOVER,
			&(flag), sizeof(uint8_t)) == -1)
	{
		printf("LANIfc.c: Setsockopt(IP_MTU_DISCOVER) Failed for UDP socket\n");
		return -1;
	}

	// if (setsockopt(socketID, IPPROTO_IP, IP_TOS,
	// 		&(pBMCInfo->LANCfs[ethIndex].Ipv4HdrParam.TypeOfService), sizeof(INT8U)) == -1)
	// {
	// 	printf("LANIfc.c: Setsockopt(IP_TOS) Failed for UDP socket\n");
	// 	return -1;
	// }

	return 0;
}

