/*
 * Licensed under the GNU General Public License version 2 with exceptions. See
 * LICENSE file in the project root for full license information
 */

/** \file
 * \brief
 * Ethernet over EtherCAT (EoE) module.
 *
 * Set / Get IP functions
 * Blocking send/receive Ethernet Frame
 * Read incoming EoE fragment to Ethernet Frame
 */

#include <stdio.h>
#include <string.h>
#include "osal.h"
#include "oshw.h"
#include "ethercat.h"

 /** EoE utility function to convert uint32 to eoe ip bytes.
 * @param[in] ip       = ip in uint32
 * @param[out] byte_ip = eoe ip 4th octet, 3ed octet, 2nd octet, 1st octet
 */
static void EOE_ip_uint32_to_byte(eoe_ip4_addr_t * ip, uint8_t * byte_ip)
{
   byte_ip[3] = eoe_ip4_addr1(ip); /* 1st octet */
   byte_ip[2] = eoe_ip4_addr2(ip); /* 2nd octet */
   byte_ip[1] = eoe_ip4_addr3(ip); /* 3ed octet */
   byte_ip[0] = eoe_ip4_addr4(ip); /* 4th octet */
}

/** EoE utility function to convert eoe ip bytes to uint32.
* @param[in] byte_ip = eoe ip 4th octet, 3ed octet, 2nd octet, 1st octet
* @param[out] ip     = ip in uint32
*/
static void EOE_ip_byte_to_uint32(uint8_t * byte_ip, eoe_ip4_addr_t * ip)
{
   EOE_IP4_ADDR_TO_U32(ip,
      byte_ip[3],  /* 1st octet */
      byte_ip[2],  /* 2nd octet */
      byte_ip[1],  /* 3ed octet */
      byte_ip[0]); /* 4th octet */
}

/** EoE fragment data handler hook. Should not block.
*
* @param[in]  context = context struct
* @param[in]  hook    = Pointer to hook function.
* @return 1
*/
int ecx_EOEdefinehook(ecx_contextt *context, void *hook)
{
   context->EOEhook = hook;
   return 1;
}

/** EoE EOE set IP, blocking. Waits for response from the slave.
*
* @param[in]  context    = Context struct
* @param[in]  slave      = Slave number
* @param[in]  port       = Port number on slave if applicable
* @param[in]  ipparam    = IP parameter data to be sent
* @param[in]  timeout    = Timeout in us, standard is EC_TIMEOUTRXM
* @return Workcounter from last slave response or returned result code 
*/
int ecx_EOEsetIp(ecx_contextt *context, uint16 slave, uint8 port, eoe_param_t * ipparam, int timeout)
{
   ec_EOEt *EOEp, *aEOEp;  
   ec_mbxbuft MbxIn, MbxOut;  
   uint16 frameinfo1, result;
   uint8 cnt, data_offset;
   uint8 flags = 0;
   int wkc;

   ec_clearmbx(&MbxIn);
   /* Empty slave out mailbox if something is in. Timout set to 0 */
   wkc = ecx_mbxreceive(context,  slave, (ec_mbxbuft *)&MbxIn, 0);
   ec_clearmbx(&MbxOut);
   aEOEp = (ec_EOEt *)&MbxIn;
   EOEp = (ec_EOEt *)&MbxOut;  
   EOEp->mbxheader.address = htoes(0x0000);
   EOEp->mbxheader.priority = 0x00;
   data_offset = EOE_PARAM_OFFSET;
   
   /* get new mailbox count value, used as session handle */
   cnt = ec_nextmbxcnt(context->slavelist[slave].mbx_cnt);
   context->slavelist[slave].mbx_cnt = cnt;

   EOEp->mbxheader.mbxtype = ECT_MBXT_EOE + MBX_HDR_SET_CNT(cnt); /* EoE */

   EOEp->frameinfo1 = htoes(EOE_HDR_FRAME_TYPE_SET(EOE_INIT_REQ) |
      EOE_HDR_FRAME_PORT_SET(port) |
      EOE_HDR_LAST_FRAGMENT);
   EOEp->frameinfo2 = 0;
  
   if (ipparam->mac_set)
   {
      flags |= EOE_PARAM_MAC_INCLUDE;
      memcpy(&EOEp->data[data_offset], ipparam->mac.addr, EOE_ETHADDR_LENGTH);
      data_offset += EOE_ETHADDR_LENGTH;
   }
   if (ipparam->ip_set)
   {
      flags |= EOE_PARAM_IP_INCLUDE;
      EOE_ip_uint32_to_byte(&ipparam->ip, &EOEp->data[data_offset]);
      data_offset += EOE_IP4_LENGTH;
   }
   if (ipparam->subnet_set)
   {
      flags |= EOE_PARAM_SUBNET_IP_INCLUDE;
      EOE_ip_uint32_to_byte(&ipparam->subnet, &EOEp->data[data_offset]);
      data_offset += EOE_IP4_LENGTH;
   }
   if (ipparam->default_gateway_set)
   {
      flags |= EOE_PARAM_DEFAULT_GATEWAY_INCLUDE;
      EOE_ip_uint32_to_byte(&ipparam->default_gateway, &EOEp->data[data_offset]);
      data_offset += EOE_IP4_LENGTH;
   }
   if (ipparam->dns_ip_set)
   {
      flags |= EOE_PARAM_DNS_IP_INCLUDE;
      EOE_ip_uint32_to_byte(&ipparam->dns_ip, &EOEp->data[data_offset]);
      data_offset += EOE_IP4_LENGTH;
   }
   if (ipparam->dns_name_set)
   {
      /* TwinCAT include EOE_DNS_NAME_LENGTH chars even if name is shorter */
      flags |= EOE_PARAM_DNS_NAME_INCLUDE;
      memcpy(&EOEp->data[data_offset], (void *)ipparam->dns_name, EOE_DNS_NAME_LENGTH);
      data_offset += EOE_DNS_NAME_LENGTH;
   }

   EOEp->mbxheader.length = htoes(EOE_PARAM_OFFSET + data_offset);
   EOEp->data[0] = flags;

   /* send EoE request to slave */
   wkc = ecx_mbxsend(context, slave, (ec_mbxbuft *)&MbxOut, EC_TIMEOUTTXM);

   if (wkc > 0) /* succeeded to place mailbox in slave ? */
   {
      /* clean mailboxbuffer */
      ec_clearmbx(&MbxIn);
      /* read slave response */
      wkc = ecx_mbxreceive(context, slave, (ec_mbxbuft *)&MbxIn, timeout);
      if (wkc > 0) /* succeeded to read slave response ? */
      {
         /* slave response should be FoE */
         if ((aEOEp->mbxheader.mbxtype & 0x0f) == ECT_MBXT_EOE)
         {
            frameinfo1 = etohs(aEOEp->frameinfo1);
            result = etohs(aEOEp->result);
            if ((EOE_HDR_FRAME_TYPE_GET(frameinfo1) != EOE_INIT_RESP) ||
                (result != EOE_RESULT_SUCCESS))
            {
               wkc = -result;
            }
         }
         else
         {
            /* unexpected mailbox received */
            wkc = -EC_ERR_TYPE_PACKET_ERROR;
         }
      }
   }
   return wkc;
}

/** EoE EOE get IP, blocking. Waits for response from the slave.
*
* @param[in]  context    = Context struct
* @param[in]  slave      = Slave number
* @param[in]  port       = Port number on slave if applicable
* @param[out] ipparam    = IP parameter data retrived from slave
* @param[in]  timeout    = Timeout in us, standard is EC_TIMEOUTRXM
* @return Workcounter from last slave response or returned result code
*/
int ecx_EOEgetIp(ecx_contextt *context, uint16 slave, uint8 port, eoe_param_t * ipparam, int timeout)
{
   ec_EOEt *EOEp, *aEOEp;
   ec_mbxbuft MbxIn, MbxOut;
   uint16 frameinfo1, eoedatasize;
   uint8 cnt, data_offset;
   uint8 flags = 0;
   int wkc;

   /* Empty slave out mailbox if something is in. Timout set to 0 */
   wkc = ecx_mbxreceive(context, slave, (ec_mbxbuft *)&MbxIn, 0);
   ec_clearmbx(&MbxOut);
   aEOEp = (ec_EOEt *)&MbxIn;
   EOEp = (ec_EOEt *)&MbxOut;
   EOEp->mbxheader.address = htoes(0x0000);
   EOEp->mbxheader.priority = 0x00;
   data_offset = EOE_PARAM_OFFSET;

   /* get new mailbox count value, used as session handle */
   cnt = ec_nextmbxcnt(context->slavelist[slave].mbx_cnt);
   context->slavelist[slave].mbx_cnt = cnt;

   EOEp->mbxheader.mbxtype = ECT_MBXT_EOE + MBX_HDR_SET_CNT(cnt); /* EoE */

   EOEp->frameinfo1 = htoes(EOE_HDR_FRAME_TYPE_SET(EOE_GET_IP_PARAM_REQ) |
      EOE_HDR_FRAME_PORT_SET(port) |
      EOE_HDR_LAST_FRAGMENT);
   EOEp->frameinfo2 = 0;

   EOEp->mbxheader.length = htoes(0x0004); 
   EOEp->data[0] = flags;

   /* send EoE request to slave */
   wkc = ecx_mbxsend(context, slave, (ec_mbxbuft *)&MbxOut, EC_TIMEOUTTXM);
   if (wkc > 0) /* succeeded to place mailbox in slave ? */
   {
      /* clean mailboxbuffer */
      ec_clearmbx(&MbxIn);
      /* read slave response */
      wkc = ecx_mbxreceive(context, slave, (ec_mbxbuft *)&MbxIn, timeout);
      if (wkc > 0) /* succeeded to read slave response ? */
      {
         /* slave response should be FoE */
         if ((aEOEp->mbxheader.mbxtype & 0x0f) == ECT_MBXT_EOE)
         {
            frameinfo1 = etohs(aEOEp->frameinfo1);
            eoedatasize = etohs(aEOEp->mbxheader.length) - 0x0004;
            if (EOE_HDR_FRAME_TYPE_GET(frameinfo1) != EOE_GET_IP_PARAM_RESP)
            {
               wkc = -EOE_RESULT_UNSUPPORTED_FRAME_TYPE;
            }
            else
            {
               flags = aEOEp->data[0];
               if (flags & EOE_PARAM_MAC_INCLUDE)
               {
                  memcpy(ipparam->mac.addr, 
                     &aEOEp->data[data_offset], 
                     EOE_ETHADDR_LENGTH);
                  ipparam->mac_set = 1;
                  data_offset += EOE_ETHADDR_LENGTH;
               }
               if (flags & EOE_PARAM_IP_INCLUDE)
               {
                  EOE_ip_byte_to_uint32(&aEOEp->data[data_offset],
                     &ipparam->ip);
                  ipparam->ip_set = 1;
                  data_offset += EOE_IP4_LENGTH;
               }
               if (flags & EOE_PARAM_SUBNET_IP_INCLUDE)
               {
                  EOE_ip_byte_to_uint32(&aEOEp->data[data_offset],
                     &ipparam->subnet);
                  ipparam->subnet_set = 1;
                  data_offset += EOE_IP4_LENGTH;
               }
               if (flags & EOE_PARAM_DEFAULT_GATEWAY_INCLUDE)
               {
                  EOE_ip_byte_to_uint32(&aEOEp->data[data_offset],
                     &ipparam->default_gateway);
                  ipparam->default_gateway_set = 1;
                  data_offset += EOE_IP4_LENGTH;
               }
               if (flags & EOE_PARAM_DNS_IP_INCLUDE)
               {
                  EOE_ip_byte_to_uint32(&aEOEp->data[data_offset],
                     &ipparam->dns_ip);
                  ipparam->dns_ip_set = 1;
                  data_offset += EOE_IP4_LENGTH;
               }
               if (flags & EOE_PARAM_DNS_NAME_INCLUDE)
               {
                  uint16_t dns_len;
                  if ((eoedatasize - data_offset) < EOE_DNS_NAME_LENGTH)
                  {
                     dns_len = (eoedatasize - data_offset);
                  }
                  else
                  {
                     dns_len = EOE_DNS_NAME_LENGTH;
                  }     
                  /* Assume ZERO terminated string */
                  memcpy(ipparam->dns_name, &aEOEp->data[data_offset], dns_len);
                  ipparam->dns_name_set = 1;
                  data_offset += EOE_DNS_NAME_LENGTH;
               }
               /* Something os not correct, flag the error */
               if(data_offset > eoedatasize)
               {
                  wkc = -EC_ERR_TYPE_MBX_ERROR;
               }
            }
         }
         else
         {
            /* unexpected mailbox received */
            wkc = -EC_ERR_TYPE_PACKET_ERROR;
         }
      }
   }
   return wkc;
}

/** EoE ethernet buffer write, blocking. 
*
* If the buffer is larger than the mailbox size then the buffer is sent in 
* several fragments. The function will split the buf data in fragments and
* send them to the slave one by one.
*
* @param[in]  context    = context struct
* @param[in]  slave      = Slave number
* @param[in]  port       = Port number on slave if applicable
* @param[in]  psize      = Size in bytes of parameter buffer.
* @param[in]  p          = Pointer to parameter buffer
* @param[in]  timeout    = Timeout in us, standard is EC_TIMEOUTRXM
* @return Workcounter from last slave transmission
*/
int ecx_EOEsend(ecx_contextt *context, uint16 slave, uint8 port, int psize, void *p, int timeout)
{
   ec_EOEt *EOEp;
   ec_mbxbuft MbxOut;
   uint16 frameinfo1, frameinfo2;
   uint8 cnt, txfragmentno;  
   boolean  NotLast;
   int wkc, maxdata, txframesize, txframeoffset;
   const uint8 * buf = p;
   static uint8_t txframeno = 0;

   ec_clearmbx(&MbxOut);
   EOEp = (ec_EOEt *)&MbxOut;
   EOEp->mbxheader.address = htoes(0x0000);
   EOEp->mbxheader.priority = 0x00;
   /* data section=mailbox size - 6 mbx - 4 EoEh */
   maxdata = context->slavelist[slave].mbx_l - 0x0A; 
   txframesize = psize;
   txfragmentno = 0;
   txframeoffset = 0;
   NotLast = TRUE;

   do
   {
      txframesize = psize - txframeoffset;
      if (txframesize > maxdata)
      {
         /* Adjust to even 32-octect blocks */
         txframesize = ((maxdata >> 5) << 5);
      }

      if (txframesize == (psize - txframeoffset))
      {
         frameinfo1 = (EOE_HDR_LAST_FRAGMENT_SET(1) | EOE_HDR_FRAME_PORT_SET(port));
         NotLast = FALSE;
      }
      else
      {
         frameinfo1 = EOE_HDR_FRAME_PORT_SET(port);
      }

      frameinfo2 = EOE_HDR_FRAG_NO_SET(txfragmentno);
      if (txfragmentno > 0)
      {
         frameinfo2 = frameinfo2 | (EOE_HDR_FRAME_OFFSET_SET((txframeoffset >> 5)));
      }
      else
      {
         frameinfo2 = frameinfo2 | (EOE_HDR_FRAME_OFFSET_SET(((psize + 31) >> 5)));
         txframeno++;
      }
      frameinfo2 = frameinfo2 | EOE_HDR_FRAME_NO_SET(txframeno);

      /* get new mailbox count value, used as session handle */
      cnt = ec_nextmbxcnt(context->slavelist[slave].mbx_cnt);
      context->slavelist[slave].mbx_cnt = cnt;

      EOEp->mbxheader.length = htoes((uint16)(4 + txframesize)); /* no timestamp */
      EOEp->mbxheader.mbxtype = ECT_MBXT_EOE + MBX_HDR_SET_CNT(cnt); /* EoE */

      EOEp->frameinfo1 = htoes(frameinfo1);
      EOEp->frameinfo2 = htoes(frameinfo2);

      memcpy(EOEp->data, &buf[txframeoffset], txframesize);

      /* send EoE request to slave */
      wkc = ecx_mbxsend(context, slave, (ec_mbxbuft *)&MbxOut, timeout);
      if ((NotLast == TRUE)  && (wkc > 0))
      {
         txframeoffset += txframesize;
         txfragmentno++;
      }
   } while ((NotLast == TRUE) && (wkc > 0));
   
   return wkc;
}


/** EoE ethernet buffer read, blocking.
*
* If the buffer is larger than the mailbox size then the buffer is received 
* by several fragments. The function will assamble the fragments into
* a complete Ethernet buffer.
*
* @param[in]     context = context struct
* @param[in]     slave   = Slave number
* @param[in]     port    = Port number on slave if applicable
* @param[in,out] psize   = Size in bytes of parameter buffer.
* @param[in]     p       = Pointer to parameter buffer
* @param[in]     timeout = Timeout in us, standard is EC_TIMEOUTRXM
* @return Workcounter from last slave response or error code
*/
int ecx_EOErecv(ecx_contextt *context, uint16 slave, uint8 port, int * psize, void *p, int timeout)
{
   ec_EOEt *aEOEp;
   ec_mbxbuft MbxIn;
   uint16 frameinfo1, frameinfo2;
   uint8 rxfragmentno, rxframeno;
   boolean NotLast;
   int wkc, buffersize, rxframesize, rxframeoffset, eoedatasize;
   uint8 * buf = p;
   
   ec_clearmbx(&MbxIn);
   aEOEp = (ec_EOEt *)&MbxIn;
   NotLast = TRUE;
   buffersize = *psize;
   rxfragmentno = 0;
   
   /* Hang for a while if nothing is in */
   wkc = ecx_mbxreceive(context, slave, (ec_mbxbuft *)&MbxIn, timeout);

   while ((wkc > 0) && (NotLast == TRUE))
   {
      /* slave response should be FoE */
      if ((aEOEp->mbxheader.mbxtype & 0x0f) == ECT_MBXT_EOE)
      {
        
         eoedatasize = etohs(aEOEp->mbxheader.length) - 0x00004;
         frameinfo1 = etohs(aEOEp->frameinfo1);
         frameinfo2 = etohs(aEOEp->frameinfo2);

         if (rxfragmentno != EOE_HDR_FRAG_NO_GET(frameinfo2))
         {
            if (EOE_HDR_FRAG_NO_GET(frameinfo2) > 0)
            {
               wkc = -EC_ERR_TYPE_EOE_INVALID_RX_DATA;
               /* Exit here*/
               break;
            }
         }

         if (rxfragmentno == 0)
         {
            rxframeoffset = 0;
            rxframeno = EOE_HDR_FRAME_NO_GET(frameinfo2);
            rxframesize = (EOE_HDR_FRAME_OFFSET_GET(frameinfo2) << 5);
            if (rxframesize > buffersize)
            {
               wkc = -EC_ERR_TYPE_EOE_INVALID_RX_DATA;
               /* Exit here*/
               break;
            }
            if (port != EOE_HDR_FRAME_PORT_GET(frameinfo1))
            {
               wkc = -EC_ERR_TYPE_EOE_INVALID_RX_DATA;
               /* Exit here*/
               break;
            }
         }
         else
         {
            if (rxframeno != EOE_HDR_FRAME_NO_GET(frameinfo2))
            {
               wkc = -EC_ERR_TYPE_EOE_INVALID_RX_DATA;
               /* Exit here*/
               break;
            }
            else if (rxframeoffset != (EOE_HDR_FRAME_OFFSET_GET(frameinfo2) << 5))
            {
               wkc = -EC_ERR_TYPE_EOE_INVALID_RX_DATA;
               /* Exit here*/
               break;
            }
         }

         if ((rxframeoffset + eoedatasize) <= buffersize)
         {
            memcpy(&buf[rxframeoffset], aEOEp->data, eoedatasize);
            rxframeoffset += eoedatasize;
            rxfragmentno++;
         }

         if (EOE_HDR_LAST_FRAGMENT_GET(frameinfo1))
         {
            /* Remove timestamp */
            if (EOE_HDR_TIME_APPEND_GET(frameinfo1))
            {
               rxframeoffset -= 4;
            }
            NotLast = FALSE;
            *psize = rxframeoffset;
         }
         else
         {
            /* Hang for a while if nothing is in */
            wkc = ecx_mbxreceive(context, slave, (ec_mbxbuft *)&MbxIn, timeout);
         }
      }
      else
      {
         /* unexpected mailbox received */
         wkc = -EC_ERR_TYPE_PACKET_ERROR;
      }
   }
   return wkc;
}

/** EoE mailbox fragment read
*
* Will take the data in incoming mailbox buffer and copy to destination 
* Ethernet frame buffer at given offset and update current fragment variables
*
* @param[in] MbxIn             = Received mailbox containing fragment data
* @param[in,out] rxfragmentno  = Fragment number
* @param[in,out] rxframesize   = Frame size
* @param[in,out] rxframeoffset = Frame offset
* @param[in,out] rxframeno     = Frame number
* @param[in,out] psize         = Size in bytes of frame buffer.
* @param[out] p                = Pointer to frame buffer
* @return 0= if fragment OK, >0 if last fragment, <0 on error
*/
int ecx_EOEreadfragment(
   ec_mbxbuft * MbxIn,
   uint8 * rxfragmentno,
   uint16 * rxframesize, 
   uint16 * rxframeoffset, 
   uint16 * rxframeno,
   int * psize, 
   void *p)
{  
   uint16 frameinfo1, frameinfo2,  eoedatasize;
   int wkc;
   ec_EOEt * aEOEp;
   uint8 * buf;
   
   aEOEp = (ec_EOEt *)MbxIn;
   buf = p;
   wkc = 0;

   /* slave response should be EoE */
   if ((aEOEp->mbxheader.mbxtype & 0x0f) == ECT_MBXT_EOE)
   {
      eoedatasize = etohs(aEOEp->mbxheader.length) - 0x00004;
      frameinfo1 = etohs(aEOEp->frameinfo1);
      frameinfo2 = etohs(aEOEp->frameinfo2);

      /* Retrive fragment number, is it what we expect? */
      if (*rxfragmentno != EOE_HDR_FRAG_NO_GET(frameinfo2))
      {
         /* If expected fragment number is not 0, reset working variables */
         if (*rxfragmentno != 0)
         {
            *rxfragmentno = 0;
            *rxframesize = 0;
            *rxframeoffset = 0;
            *rxframeno = 0;
         }

         /* If incoming fragment number is not 0 we can't recover, exit */
         if (EOE_HDR_FRAG_NO_GET(frameinfo2) > 0)
         {
            wkc = -EC_ERR_TYPE_EOE_INVALID_RX_DATA;
            return wkc;
         }
      }

      /* Is it a new frame?*/
      if (*rxfragmentno == 0)
      {
         *rxframesize = (uint16)(EOE_HDR_FRAME_OFFSET_GET(frameinfo2) << 5);
         *rxframeoffset = 0;
         *rxframeno = EOE_HDR_FRAME_NO_GET(frameinfo2);
      }
      else
      {
         /* If we're inside a frame, make sure it is the same */
         if (*rxframeno != EOE_HDR_FRAME_NO_GET(frameinfo2))
         {
            *rxfragmentno = 0;
            *rxframesize = 0;
            *rxframeoffset = 0;
            *rxframeno = 0;
            wkc = -EC_ERR_TYPE_EOE_INVALID_RX_DATA;
            return wkc;
         }
         else if (*rxframeoffset != (EOE_HDR_FRAME_OFFSET_GET(frameinfo2) << 5))
         {
            *rxfragmentno = 0;
            *rxframesize = 0;
            *rxframeoffset = 0;
            *rxframeno = 0;
            wkc = -EC_ERR_TYPE_EOE_INVALID_RX_DATA;
            return wkc;
         }
      }

      /* Make sure we're inside expected frame size */
      if (((*rxframeoffset + eoedatasize) <= *rxframesize) && 
         ((*rxframeoffset + eoedatasize) <= *psize))
      {
         memcpy(&buf[*rxframeoffset], aEOEp->data, eoedatasize);
         *rxframeoffset += eoedatasize;
         *rxfragmentno += 1;
      }

      /* Is it the last fragment */
      if (EOE_HDR_LAST_FRAGMENT_GET(frameinfo1))
      {
         /* Remove timestamp */
         if (EOE_HDR_TIME_APPEND_GET(frameinfo1))
         {
            *rxframeoffset -= 4;
         }
         *psize = *rxframeoffset;
         *rxfragmentno = 0;
         *rxframesize = 0;
         *rxframeoffset = 0;
         *rxframeno = 0;
         wkc = 1;
      }
   }
   else
   {
      /* unexpected mailbox received */
      wkc = -EC_ERR_TYPE_PACKET_ERROR;
   }
   return wkc;
}
