/*
 * 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.
 */

#include <cc.h>
#include <string.h>
#include "esc.h"
#include "esc_eoe.h"


#if defined(EC_BIG_ENDIAN)
#define EOE_HTONS(x) (x)
#define EOE_NTOHS(x) (x)
#define EOE_HTONL(x) (x)
#define EOE_NTOHL(x) (x)
#else
#define EOE_HTONS(x) ((((x) & 0x00ffUL) << 8) | (((x) & 0xff00UL) >> 8))
#define EOE_NTOHS(x) EOE_HTONS(x)
#define EOE_HTONL(x) ((((x) & 0x000000ffUL) << 24) | \
                     (((x) & 0x0000ff00UL) <<  8) | \
                     (((x) & 0x00ff0000UL) >>  8) | \
                     (((x) & 0xff000000UL) >> 24))
#define EOE_NTOHL(x) EOE_HTONL(x)
#endif /* #if defined(EC_BIG_ENDIAN) */

#define EOE_MAKEU32(a,b,c,d) (((uint32_t)((a) & 0xff) << 24) | \
                            ((uint32_t)((b) & 0xff) << 16) | \
                            ((uint32_t)((c) & 0xff) << 8)  | \
                            (uint32_t)((d) & 0xff))

/** Get one byte from the 4-byte address */
#define eoe_ip4_addr1(ipaddr) (((const uint8_t*)(&(ipaddr)->addr))[0])
#define eoe_ip4_addr2(ipaddr) (((const uint8_t*)(&(ipaddr)->addr))[1])
#define eoe_ip4_addr3(ipaddr) (((const uint8_t*)(&(ipaddr)->addr))[2])
#define eoe_ip4_addr4(ipaddr) (((const uint8_t*)(&(ipaddr)->addr))[3])

/** Set an IP address given by the four byte-parts */
#define EOE_IP4_ADDR_TO_U32(ipaddr,a,b,c,d)  \
   (ipaddr)->addr = EOE_HTONL(EOE_MAKEU32(a,b,c,d))

/** Header frame info 1 */
#define EOE_HDR_FRAME_TYPE_OFFSET      0
#define EOE_HDR_FRAME_TYPE             (0xF << 0)
#define EOE_HDR_FRAME_TYPE_SET(x)      (((x) & 0xF) << 0)
#define EOE_HDR_FRAME_TYPE_GET(x)      (((x) >> 0) & 0xF)
#define EOE_HDR_FRAME_PORT_OFFSET      4
#define EOE_HDR_FRAME_PORT             (0xF << 4)
#define EOE_HDR_FRAME_PORT_SET(x)      (((x) & 0xF) << 4)
#define EOE_HDR_FRAME_PORT_GET(x)      (((x) >> 4) & 0xF)
#define EOE_HDR_LAST_FRAGMENT_OFFSET   8
#define EOE_HDR_LAST_FRAGMENT          (0x1 << 8)
#define EOE_HDR_LAST_FRAGMENT_SET(x)   (((x) & 0x1) << 8)
#define EOE_HDR_LAST_FRAGMENT_GET(x)   (((x) >> 8) & 0x1)
#define EOE_HDR_TIME_APPEND_OFFSET     9
#define EOE_HDR_TIME_APPEND            (0x1 << 9)
#define EOE_HDR_TIME_APPEND_SET(x)     (((x) & 0x1) << 9)
#define EOE_HDR_TIME_APPEND_GET(x)     (((x) >> 9) & 0x1)
#define EOE_HDR_TIME_REQUEST_OFFSET    10
#define EOE_HDR_TIME_REQUEST           (0x1 << 10)
#define EOE_HDR_TIME_REQUEST_SET(x)    (((x) & 0x1) << 10)
#define EOE_HDR_TIME_REQUEST_GET(x)    (((x) >> 10) & 0x1)

/** Header frame info 2 */
#define EOE_HDR_FRAG_NO_OFFSET         0
#define EOE_HDR_FRAG_NO                (0x3F << 0)
#define EOE_HDR_FRAG_NO_SET(x)         (((x) & 0x3F) << 0)
#define EOE_HDR_FRAG_NO_GET(x)         (((x) >> 0) & 0x3F)
#define EOE_HDR_FRAME_OFFSET_OFFSET    6
#define EOE_HDR_FRAME_OFFSET           (0x3F << 6)
#define EOE_HDR_FRAME_OFFSET_SET(x)    (((x) & 0x3F) << 6)
#define EOE_HDR_FRAME_OFFSET_GET(x)    (((x) >> 6) & 0x3F)
#define EOE_HDR_FRAME_NO_OFFSET        12
#define EOE_HDR_FRAME_NO               (0xF << 12)
#define EOE_HDR_FRAME_NO_SET(x)        (((x) & 0xF) << 12)
#define EOE_HDR_FRAME_NO_GET(x)        (((x) >> 12) & 0xF)

/** EOE param */
#define EOE_PARAM_OFFSET                  4
#define EOE_PARAM_MAC_INCLUDE             (0x1 << 0)
#define EOE_PARAM_IP_INCLUDE              (0x1 << 1)
#define EOE_PARAM_SUBNET_IP_INCLUDE       (0x1 << 2)
#define EOE_PARAM_DEFAULT_GATEWAY_INCLUDE (0x1 << 3)
#define EOE_PARAM_DNS_IP_INCLUDE          (0x1 << 4)
#define EOE_PARAM_DNS_NAME_INCLUDE        (0x1 << 5)

/** EoE frame types */
#define EOE_FRAG_DATA                  0
#define EOE_INIT_RESP_TIMESTAMP        1
#define EOE_INIT_REQ                   2 /* Spec SET IP REQ */
#define EOE_INIT_RESP                  3 /* Spec SET IP RESP */
#define EOE_SET_ADDR_FILTER_REQ        4
#define EOE_SET_ADDR_FILTER_RESP       5
#define EOE_GET_IP_PARAM_REQ           6
#define EOE_GET_IP_PARAM_RESP          7
#define EOE_GET_ADDR_FILTER_REQ        8
#define EOE_GET_ADDR_FILTER_RESP       9

/** Define number of ports available.(Only one is supported currently */
#define EOE_NUMBER_OF_PORTS   1
#define EOE_PORT_INDEX(x)     ((x > 0) ? (x - 1) : 0)
/** DNS length according to ETG 1000.6 */
#define EOE_DNS_NAME_LENGTH  32
/** Ethernet address length not including VLAN */
#define EOE_ETHADDR_LENGTH    6
/** IPv4 address length */
#define EOE_IP4_LENGTH        sizeof(uint32_t)

/** EOE ip4 address in network order */
struct eoe_ip4_addr {
  uint32_t addr;
};
typedef struct eoe_ip4_addr eoe_ip4_addr_t;

/** EOE ethernet address */
CC_PACKED_BEGIN
typedef struct CC_PACKED eoe_ethaddr
{
  uint8_t addr[EOE_ETHADDR_LENGTH];
} eoe_ethaddr_t;
CC_PACKED_END

typedef struct
{
   /** Pointer to current RX buffer to fill */
   eoe_pbuf_t rxebuf;
   /** Pointer to current TX buff to Send */
   eoe_pbuf_t txebuf;

   /** Current RX fragment number */
   uint8_t rxfragmentno;
   /** Complete RX frame size of current frame */
   uint16_t rxframesize;
   /** Current RX data offset in frame */
   uint16_t rxframeoffset;
   /** Current RX frame number */
   uint16_t rxframeno;

   /** Current TX fragment number */
   uint8_t txfragmentno;
   /** Complete TX frame size of current frame */
   uint16_t txframesize;
   /** Current TX data offset in frame */
   uint16_t txframeoffset;
} _EOEvar;

/** EoE IP request structure */
typedef struct eoe_param
{
   uint8_t mac_set:1;
   uint8_t ip_set:1;
   uint8_t subnet_set:1;
   uint8_t default_gateway_set:1;
   uint8_t dns_ip_set:1;
   uint8_t dns_name_set:1;
   eoe_ethaddr_t mac;
   eoe_ip4_addr_t ip;
   eoe_ip4_addr_t subnet;
   eoe_ip4_addr_t default_gateway;
   eoe_ip4_addr_t dns_ip;
   char dns_name[EOE_DNS_NAME_LENGTH];
} eoe_param_t;

/** Main EoE status data array. Structure gets filled with current information
 * variables during EoE receive and send operations.
 */
static _EOEvar EOEvar;

/** Main FoE configuration pointer data array. Structure is allocated and filled
 * by the application defining what preferences it requires.
 */
static eoe_cfg_t * eoe_cfg;

/** Local EoE variable holding cached IP information values.
 * To be set or read from the user application, eg. TCP/IP stack.
 */
static eoe_param_t nic_ports[EOE_NUMBER_OF_PORTS];

/** Local init/reset functions on frame receive init */
static void EOE_init_rx ();
/** Local init/reset functions on frame send completion */
static void EOE_init_tx ();

/** 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 */
}

/** Get EoE cached MAC address
 *
 * @param[in] port   = get MAC for port
 * @param[out] mac   = variable to store mac in, should fit EOE_ETHADDR_LENGTH
 * @return 0= if we succeed, -1 if not set
 */
int EOE_get_mac(uint8_t port, uint8_t mac[])
{
   int ret = -1;
   int port_ix;

   if(port < EOE_NUMBER_OF_PORTS)
   {
      port_ix = EOE_PORT_INDEX(port);
      if(nic_ports[port_ix].mac_set)
      {
         memcpy(mac, nic_ports[port_ix].mac.addr,
               sizeof(nic_ports[port_ix].mac));
         nic_ports[port_ix].mac_set = 1;
         ret = 0;
      }
   }
   return ret;
}

/** Set EoE cached MAC address
 *
 * @param[in] port   = get MAC for port
 * @param[in] mac    = mac address to store
 * @return 0= if we succeed, else -1.
 */
int EOE_ecat_set_mac(uint8_t port, uint8_t mac[])
{
   int ret = -1;
   int port_ix;
   if(port < EOE_NUMBER_OF_PORTS)
   {
      port_ix = EOE_PORT_INDEX(port);
      memcpy(nic_ports[port_ix].mac.addr, mac,
            sizeof(nic_ports[port_ix].mac));
      ret = 0;
   }
   return ret;
}

/** Get EoE cached ip address
 *
 * @param[in] port  = get ip address for port
 * @param[out] ip   = variable to store ip in
 * @return 0= if we succeed, -1 if not set
 */
int EOE_ecat_get_ip(uint8_t port, uint32_t * ip)
{
   int ret = -1;
   int port_ix;
   if(port < EOE_NUMBER_OF_PORTS)
   {
      port_ix = EOE_PORT_INDEX(port);
      if(nic_ports[port_ix].ip_set)
      {
         *ip = EOE_NTOHL(nic_ports[port_ix].ip.addr);
         ret = 0;
      }
   }
   return ret;
}

/** Set EoE cached ip address
 *
 * @param[in] port   = get ip for port
 * @param[in] ip     = ip address to store
 * @return 0= if we succeed, else -1.
 */
int EOE_ecat_set_ip(uint8_t port, uint32_t  ip)
{
   int ret = -1;
   int port_ix;
   if(port < EOE_NUMBER_OF_PORTS)
   {
      port_ix = EOE_PORT_INDEX(port);
      nic_ports[port_ix].ip.addr = EOE_HTONL(ip);
      nic_ports[port_ix].ip_set = 1;
      ret = 0;
   }
   return ret;
}

/** Get EoE cached subnet ip address
 *
 * @param[in] port    = get ip address for port
 * @param[out] subnet = variable to store ip in
 * @return 0= if we succeed, -1 if not set
 */
int EOE_ecat_get_subnet(uint8_t port, uint32_t * subnet)
{
   int ret = -1;
   int port_ix;
   if(port < EOE_NUMBER_OF_PORTS)
   {
      port_ix = EOE_PORT_INDEX(port);
      if(nic_ports[port_ix].subnet_set)
      {
         *subnet = EOE_NTOHL(nic_ports[port_ix].subnet.addr);
         ret = 0;
      }
   }
   return ret;
}

/** Set EoE cached subnet ip address
 *
 * @param[in] port   = get ip for port
 * @param[in] subnet = ip address to store
 * @return 0= if we succeed, else -1.
 */
int EOE_ecat_set_subnet(uint8_t port, uint32_t subnet)
{
   int ret = -1;
   int port_ix;
   if(port < EOE_NUMBER_OF_PORTS)
   {
      port_ix = EOE_PORT_INDEX(port);
      nic_ports[port_ix].subnet.addr = EOE_HTONL(subnet);
      nic_ports[port_ix].subnet_set = 1;
      ret = 0;
   }
   return ret;
}

/** Get EoE cached default gateway ip address
 *
 * @param[in] port             = get ip address for port
 * @param[out] default_gateway = variable to store ip in
 * @return 0= if we succeed, -1 if not set
 */
int EOE_ecat_get_gateway(uint8_t port, uint32_t * default_gateway)
{
   int ret = -1;
   int port_ix;
   if(port < EOE_NUMBER_OF_PORTS)
   {
      port_ix = EOE_PORT_INDEX(port);
      if(nic_ports[port_ix].default_gateway_set)
      {
         *default_gateway =
               EOE_NTOHL(nic_ports[port_ix].default_gateway.addr);
         ret = 0;
      }
   }
   return ret;
}

/** Set EoE cached default gateway ip address
 *
 * @param[in] port            = get ip for port
 * @param[in] default_gateway = ip address to store
 * @return 0= if we succeed, else -1.
 */
int EOE_ecat_set_gateway(uint8_t port, uint32_t default_gateway)
{
   int ret = -1;
   int port_ix;
   if(port < EOE_NUMBER_OF_PORTS)
   {
      port_ix = EOE_PORT_INDEX(port);
      nic_ports[port_ix].default_gateway.addr =
            EOE_HTONL(default_gateway);
      nic_ports[port_ix].default_gateway_set = 1;
      ret = 0;
   }
   return ret;
}

/** Get EoE cached dns ip address
 *
 * @param[in] port    = get ip address for port
 * @param[out] dns_ip = variable to store ip in
 * @return 0= if we succeed, -1 if not set
 */
int EOE_ecat_get_dns_ip(uint8_t port, uint32_t * dns_ip)
{
   int ret = -1;
   int port_ix;
   if(port < EOE_NUMBER_OF_PORTS)
   {
      port_ix = EOE_PORT_INDEX(port);
      if(nic_ports[port_ix].dns_ip_set)
      {
         *dns_ip = EOE_NTOHL(nic_ports[port_ix].dns_ip.addr);
         ret = 0;
      }
   }
   return ret;
}

/** Set EoE cached dns ip address
 *
 * @param[in] port   = get ip for port
 * @param[in] dns_ip = ip address to store
 * @return 0= if we succeed, else -1.
 */
int EOE_ecat_set_dns_ip(uint8_t port, uint32_t dns_ip)
{
   int ret = -1;
   int port_ix;
   if(port < EOE_NUMBER_OF_PORTS)
   {
      port_ix = EOE_PORT_INDEX(port);
      nic_ports[port_ix].dns_ip.addr = EOE_HTONL(dns_ip);
      nic_ports[port_ix].dns_ip_set = 1;
      ret = 0;
   }
   return ret;
}

/** Get EoE cached dns name
 *
 * @param[in] port      = get dns name for port
 * @param[out] dns_name = variable to store dns name in
 * @return 0= if we succeed, -1 if not set
 */
int EOE_ecat_get_dns_name(uint8_t port, char * dns_name)
{
   int ret = -1;
   int port_ix;
   if(port < EOE_NUMBER_OF_PORTS)
   {
      port_ix = EOE_PORT_INDEX(port);
      if(nic_ports[port_ix].dns_name_set)
      {
         memcpy(dns_name,
               nic_ports[port_ix].dns_name,
               sizeof(nic_ports[port_ix].dns_name));
         ret = 0;
      }
   }
   return ret;
}

/** Set EoE cached dns name
 *
 * @param[in] port     = get dns name for port
 * @param[in] dns_name = dns name to store
 * @return 0= if we succeed, else -1.
 */
int EOE_ecat_set_dns_name(uint8_t port, char * dns_name)
{
   int ret = -1;
   int port_ix;

   if(port < EOE_NUMBER_OF_PORTS)
   {
      port_ix = EOE_PORT_INDEX(port);
      memcpy(nic_ports[port_ix].dns_name,
            dns_name,
            sizeof(nic_ports[port_ix].dns_name));
      nic_ports[port_ix].dns_name_set = 1;
      ret = 0;
   }
   return ret;
}

/** Function for sending an simple EOE response frame.
 *
 * @param[in] frametype1 = frame type of response
 * @param[in] result    = result code
 */
static void EOE_no_data_response (uint16_t frameinfo1, uint16_t result)
{
   _EOE *eoembx;
   uint8_t mbxhandle;

   /* Send back a response packet. */
   mbxhandle = ESC_claimbuffer ();
   if (mbxhandle)
   {
      eoembx = (_EOE *) &MBX[mbxhandle * ESC_MBXSIZE];
      eoembx->mbxheader.length = htoes (ESC_EOEHSIZE);
      eoembx->mbxheader.mbxtype = MBXEOE;
      eoembx->eoeheader.frameinfo1 = htoes(frameinfo1);
      eoembx->eoeheader.result = htoes(result);
      MBXcontrol[mbxhandle].state = MBXstate_outreq;
   }
}

/** EoE get IP param request handler. Will send a get IP param response.
 */
static void EOE_get_ip (void)
{
   _EOE *req_eoembx;
   _EOE *eoembx;
   uint8_t mbxhandle;
   uint16_t frameinfo1;
   uint8_t port;
   uint8_t  flags;
   uint8_t  data_offset;
   int port_ix;

   req_eoembx = (_EOE *) &MBX[0];
   frameinfo1 = etohs(req_eoembx->eoeheader.frameinfo1);
   port = EOE_HDR_FRAME_PORT_GET(frameinfo1);
   data_offset = EOE_PARAM_OFFSET;
   flags = 0;

   if(port  > EOE_NUMBER_OF_PORTS)
   {
      DPRINT("Invalid port\n");
      /* Return error response on given port */
      EOE_no_data_response((EOE_HDR_FRAME_PORT_SET(port) |
            EOE_INIT_RESP |
            EOE_HDR_LAST_FRAGMENT),
            EOE_RESULT_UNSPECIFIED_ERROR);
      return;
   }

   /* Refresh settings if needed */
   if(eoe_cfg->load_eth_settings != NULL)
   {
      (void)eoe_cfg->load_eth_settings();
   }

   /* Send back an response packet. */
   mbxhandle = ESC_claimbuffer ();
   if (mbxhandle)
   {
      eoembx = (_EOE *) &MBX[mbxhandle * ESC_MBXSIZE];
      eoembx->mbxheader.mbxtype = MBXEOE;
      MBXcontrol[mbxhandle].state = MBXstate_outreq;
      eoembx->eoeheader.frameinfo1 =
            htoes(EOE_HDR_FRAME_TYPE_SET(EOE_GET_IP_PARAM_RESP) |
                  EOE_HDR_FRAME_PORT_SET(port) |
                  EOE_HDR_LAST_FRAGMENT);
      eoembx->eoeheader.frameinfo2 = 0;

      /* include mac in get ip request */
      port_ix = EOE_PORT_INDEX(port);
      if(nic_ports[port_ix].mac_set)
      {
         flags |= EOE_PARAM_MAC_INCLUDE;
         memcpy(&eoembx->data[data_offset] ,
               nic_ports[port_ix].mac.addr,
               EOE_ETHADDR_LENGTH);
         /* Add size of mac address */
         data_offset += EOE_ETHADDR_LENGTH;

      }
      /* include ip in get ip request */
      if(nic_ports[port_ix].ip_set)
      {
         flags |= EOE_PARAM_IP_INCLUDE;
         EOE_ip_uint32_to_byte(&nic_ports[port_ix].ip,
               &eoembx->data[data_offset]);
         /* Add size of uint32 IP address */
         data_offset += EOE_IP4_LENGTH;
      }

      /* include subnet in get ip request */
      if(nic_ports[port_ix].subnet_set)
      {
         flags |= EOE_PARAM_SUBNET_IP_INCLUDE;
         EOE_ip_uint32_to_byte(&nic_ports[port_ix].subnet,
               &eoembx->data[data_offset]);
         /* Add size of uint32 IP address */
         data_offset += EOE_IP4_LENGTH;
      }

      /* include default gateway in get ip request */
      if(nic_ports[port_ix].default_gateway_set)
      {
         flags |= EOE_PARAM_DEFAULT_GATEWAY_INCLUDE;
         EOE_ip_uint32_to_byte(&nic_ports[port_ix].default_gateway,
               &eoembx->data[data_offset]);
         /* Add size of uint32 IP address */
         data_offset += EOE_IP4_LENGTH;
      }
      /* include dns ip in get ip request */
      if(nic_ports[port_ix].dns_ip_set)
      {
         flags |= EOE_PARAM_DNS_IP_INCLUDE;
         EOE_ip_uint32_to_byte(&nic_ports[port_ix].dns_ip,
               &eoembx->data[data_offset]);
         /* Add size of uint32 IP address */
         data_offset += EOE_IP4_LENGTH;
      }

      /* include dns name in get ip request */
      if(nic_ports[port_ix].dns_name_set)
      {
         /* TwinCAT include EOE_DNS_NAME_LENGTH chars even if name is shorter */
         flags |= EOE_PARAM_DNS_NAME_INCLUDE;
         memcpy(&eoembx->data[data_offset],
               nic_ports[port_ix].dns_name,
               EOE_DNS_NAME_LENGTH);
         /* Add size of dns name length */
         data_offset += EOE_DNS_NAME_LENGTH;
      }

      eoembx->data[0] = flags;
      eoembx->mbxheader.length = htoes (ESC_EOEHSIZE + data_offset);
   }
}

/** EoE set IP param request handler. Will send a set IP param response.
 */
static void EOE_set_ip (void)
{
   _EOE *eoembx;
   uint16_t eoedatasize;
   uint16_t frameinfo1;
   uint8_t port;
   uint8_t  flags;
   uint8_t  data_offset;
   uint16_t result;
   int port_ix;

   eoembx = (_EOE *) &MBX[0];
   eoedatasize = etohs(eoembx->mbxheader.length) - ESC_EOEHSIZE;
   frameinfo1 = etohs(eoembx->eoeheader.frameinfo1);
   port = EOE_HDR_FRAME_PORT_GET(frameinfo1);
   flags = eoembx->data[0];
   data_offset = EOE_PARAM_OFFSET;

   if(port  > EOE_NUMBER_OF_PORTS)
   {
      DPRINT("Invalid port\n");
      /* Return error response on given port */
      EOE_no_data_response((EOE_HDR_FRAME_PORT_SET(port) |
            EOE_INIT_RESP |
            EOE_HDR_LAST_FRAGMENT),
            EOE_RESULT_UNSPECIFIED_ERROR);
      return;
   }

   /* mac included in set ip request? */
   port_ix = EOE_PORT_INDEX(port);
   if(flags & EOE_PARAM_MAC_INCLUDE)
   {
      memcpy(&nic_ports[port_ix].mac.addr,
            &eoembx->data[data_offset],
            EOE_ETHADDR_LENGTH);
      nic_ports[port_ix].mac_set = 1;
      /* Add size of mac address */
      data_offset += EOE_ETHADDR_LENGTH;
   }
   /* ip included in set ip request? */
   if(flags & EOE_PARAM_IP_INCLUDE)
   {
      EOE_ip_byte_to_uint32(&eoembx->data[data_offset],
            &nic_ports[port_ix].ip);
      nic_ports[port_ix].ip_set = 1;
      /* Add size of uint32 IP address */
      data_offset += EOE_IP4_LENGTH;
   }
   /* subnet included in set ip request? */
   if(flags & EOE_PARAM_SUBNET_IP_INCLUDE)
   {
      EOE_ip_byte_to_uint32(&eoembx->data[data_offset],
            &nic_ports[port_ix].subnet);
      nic_ports[port_ix].subnet_set = 1;
      /* Add size of uint32 IP address */
      data_offset += EOE_IP4_LENGTH;
   }
   /* default gateway included in set ip request? */
   if(flags & EOE_PARAM_DEFAULT_GATEWAY_INCLUDE)
   {
      EOE_ip_byte_to_uint32(&eoembx->data[data_offset],
            &nic_ports[port_ix].default_gateway);
      nic_ports[port_ix].default_gateway_set = 1;
      /* Add size of uint32 IP address */
      data_offset += EOE_IP4_LENGTH;
   }
   /* dns ip included in set ip request? */
   if(flags & EOE_PARAM_DNS_IP_INCLUDE)
   {
      EOE_ip_byte_to_uint32(&eoembx->data[data_offset],
            &nic_ports[port_ix].dns_ip);
      nic_ports[port_ix].dns_ip_set = 1;
      /* Add size of uint32 IP address */
      data_offset += EOE_IP4_LENGTH;
   }
   /* dns name included in set ip request? */
   if(flags & EOE_PARAM_DNS_NAME_INCLUDE)
   {
      uint16_t dns_len = MIN((eoedatasize - data_offset), EOE_DNS_NAME_LENGTH);
      memcpy(nic_ports[port_ix].dns_name,
            &eoembx->data[data_offset],
            dns_len);
      nic_ports[port_ix].dns_name_set = 1;
      data_offset += dns_len; /* expected 1- EOE_DNS_NAME_LENGTH; */
   }

   if(data_offset > eoedatasize)
   {
      result = MBXERR_SIZETOOSHORT;
   }
   else
   {
      /* Application specific store settings function. From there
       * you typically set the IP for the TCP/IP stack */
      if(eoe_cfg->store_ethernet_settings != NULL)
      {
         result = eoe_cfg->store_ethernet_settings();
      }
      else
      {
         result = EOE_RESULT_NO_IP_SUPPORT;
      }
   }
   EOE_no_data_response((EOE_HDR_FRAME_PORT_SET(port) |
         EOE_INIT_RESP |
         EOE_HDR_LAST_FRAGMENT),
         result);
}

/** EoE receive fragment handler.
 */
static void EOE_receive_fragment (void)
{
   _EOE *eoembx;
   eoembx = (_EOE *) &MBX[0];
   uint16_t eoedatasize = etohs(eoembx->mbxheader.length) - ESC_EOEHSIZE;
   uint16_t frameinfo1 = etohs(eoembx->eoeheader.frameinfo1);
   uint16_t frameinfo2 = etohs(eoembx->eoeheader.frameinfo2);

   /* Capture error case */
   if(EOEvar.rxfragmentno != EOE_HDR_FRAG_NO_GET(frameinfo2))
   {
      DPRINT("Unexpected fragment number %d, expected: %d\n",
            EOE_HDR_FRAG_NO_GET(frameinfo2), EOEvar.rxfragmentno);
      /* Clean up existing saved data */
      if(EOEvar.rxfragmentno != 0)
      {
         EOE_init_rx();
      }
      /* Skip fragment if not start of new frame */
      if(EOE_HDR_FRAG_NO_GET(frameinfo2) > 0)
      {
         return;
      }
   }

   /* Start of new frame at fragment 0 */
   if(EOEvar.rxfragmentno == 0)
   {
      EOEvar.rxframesize = (EOE_HDR_FRAME_OFFSET_GET(frameinfo2) << 5);

      if(EOEvar.rxebuf.payload != NULL)
      {
         EOEvar.rxebuf.len = EOEvar.rxframesize;
         EOEvar.rxframeoffset = 0;
         EOEvar.rxframeno = EOE_HDR_FRAME_NO_GET(frameinfo2);
      }
   }
   /* In frame fragment received */
   else
   {
      uint16_t offset = (EOE_HDR_FRAME_OFFSET_GET(frameinfo2) << 5);
      /* Validate received fragment */
      if(EOEvar.rxframeno != EOE_HDR_FRAME_NO_GET(frameinfo2))
      {
         DPRINT("Unexpected frame number %d, expected: %d\n",
               EOE_HDR_FRAME_NO_GET(frameinfo2), EOEvar.rxframeno);
         EOE_init_rx ();
         return;
      }
      else if(EOEvar.rxframeoffset != offset)
      {
         DPRINT("Unexpected frame offset %d, expected: %d\n",
               offset, EOEvar.rxframeoffset);
         EOE_init_rx ();
         return;
      }
   }

   /* Check so allocated buffer is sufficient */
   if ((EOEvar.rxframeoffset + eoedatasize) <= EOEvar.rxframesize)
   {
      memcpy((uint8_t *)(EOEvar.rxebuf.payload + EOEvar.rxframeoffset),
            eoembx->data,
            eoedatasize);
      EOEvar.rxframeoffset += eoedatasize;
      EOEvar.rxfragmentno++;
   }
   else
   {
      DPRINT("Size of data exceed available buffer size\n");
      EOE_init_rx ();
      return;
   }

   if(EOE_HDR_LAST_FRAGMENT_GET(frameinfo1))
   {
      /* Remove time stamp, TODO support for time stamp? */
      if(EOE_HDR_TIME_APPEND_GET(frameinfo1))
      {
         EOEvar.rxframeoffset -= 4;
      }
      EOEvar.rxebuf.len =  EOEvar.rxframeoffset;
      eoe_cfg->handle_recv_buffer(EOE_HDR_FRAME_PORT_GET(frameinfo1),
            &EOEvar.rxebuf);
      /* Pass ownership of buf to receive function */
      EOEvar.rxebuf.payload = NULL;
      EOE_init_rx ();
   }
}

/** EoE send fragment handler.
 */
static void EOE_send_fragment ()
{
   _EOE *eoembx;
   uint8_t mbxhandle;
   int len;
   int len_to_send;
   uint16_t frameinfo1;
   uint16_t frameinfo2;
   static uint8_t frameno = 0;

   /* Do we have a current transfer on-going */
   if(EOEvar.txebuf.payload == NULL)
   {
      /* Fetch a buffer if available */
      len = eoe_cfg->fetch_send_buffer(0, &EOEvar.txebuf);
      if(len > 0)
      {
         EOEvar.txframesize = len;
      }
      else
      {
         return;
      }
   }

   /* Process the frame if we can get a free mailbox */
   mbxhandle = ESC_claimbuffer ();
   if (mbxhandle)
   {
      len_to_send = EOEvar.txframesize - EOEvar.txframeoffset;
      if((len_to_send + ESC_EOEHSIZE + ESC_MBXHSIZE) > ESC_MBXSIZE)
      {
         /* Adjust to len in whole 32 octet blocks to fit specification*/
         len_to_send =
               (((ESC_MBXSIZE - ESC_EOEHSIZE - ESC_MBXHSIZE) >> 5) << 5);
      }

      /* TODO: port handling? */
      if(len_to_send == (EOEvar.txframesize - EOEvar.txframeoffset))
      {
         frameinfo1 = EOE_HDR_LAST_FRAGMENT_SET(1);
      }
      else
      {
         frameinfo1 = 0;
      }

      /* Set fragment number */
      frameinfo2 = EOE_HDR_FRAG_NO_SET(EOEvar.txfragmentno);

      /* Set complete size for fragment 0 or offset for in frame fragments */
      if(EOEvar.txfragmentno > 0)
      {
         frameinfo2 |= (EOE_HDR_FRAME_OFFSET_SET((EOEvar.txframeoffset >> 5)));
      }
      else
      {
         frameinfo2 |=
               (EOE_HDR_FRAME_OFFSET_SET(((EOEvar.txframesize + 31) >> 5)));
         frameno++;
      }

      /* Set frame number */
      frameinfo2 = frameinfo2 | EOE_HDR_FRAME_NO_SET(frameno);

      eoembx = (_EOE *) &MBX[mbxhandle * ESC_MBXSIZE];
      eoembx->mbxheader.length = htoes (len_to_send + ESC_EOEHSIZE);
      eoembx->mbxheader.mbxtype = MBXEOE;
      eoembx->eoeheader.frameinfo1 = htoes(frameinfo1);
      eoembx->eoeheader.frameinfo2 = htoes(frameinfo2);

      /* Copy data to mailbox */
      memcpy(eoembx->data,
            &EOEvar.txebuf.payload[EOEvar.txframeoffset],
            len_to_send);
      MBXcontrol[mbxhandle].state = MBXstate_outreq;

      /* Did we complete the frame? */
      if(len_to_send == (EOEvar.txframesize - EOEvar.txframeoffset))
      {
         EOE_init_tx ();
      }
      else
      {
         EOEvar.txframeoffset += len_to_send;
         EOEvar.txfragmentno += 1;
      }
      if(eoe_cfg->fragment_sent_event != NULL)
      {
         eoe_cfg->fragment_sent_event();
      }
   }
}

/** Initialize by clearing all current status variables and fetch new buffer.
 */
static void EOE_init_rx ()
{
   /* Reset RX transfer status variables */
   EOEvar.rxfragmentno = 0;
   EOEvar.rxframesize = 0;
   EOEvar.rxframeoffset = 0;
   EOEvar.rxframeno = 0;

   /* Fetch buffer */
   if(EOEvar.rxebuf.payload == NULL)
   {
      if(eoe_cfg->get_buffer != NULL)
      {
         /* TODO: verify size VS buffer size */
         eoe_cfg->get_buffer(&EOEvar.rxebuf);
      }
   }
}

/** Initialize by clearing all current status variables and release old buffer.
 */
static void EOE_init_tx ()
{
   /* Reset TX transfer status variables */
   EOEvar.txfragmentno = 0;
   EOEvar.txframesize = 0;
   EOEvar.txframeoffset = 0;

   /* Release what seems as an abandoned buffer */
   if((EOEvar.txebuf.payload != NULL))
   {
      if(eoe_cfg->free_buffer != NULL)
      {
         eoe_cfg->free_buffer(&EOEvar.txebuf);
         EOEvar.txebuf.pbuf = NULL;
         EOEvar.txebuf.payload = NULL;
         EOEvar.txebuf.len = 0;
      }
   }
}

/** Initialize by clearing all current status variables.
 */
void EOE_init ()
{
   DPRINT("EOE_init\n");
   EOE_init_tx ();
   EOE_init_rx ();
}

/** Function copying the application configuration variable
 * to the EoE module local pointer variable.
 *
 * @param[in] cfg       = Pointer to by the Application static declared
 * configuration variable holding application specific details.
 */
void EOE_config (eoe_cfg_t * cfg)
{
   eoe_cfg = cfg;
}

/** Main EoE receive function checking the status on current mailbox buffers
 * carrying data, distributing the mailboxes to appropriate EOE functions
 * depending on requested frametype.
 */
void ESC_eoeprocess (void)
{
   _MBXh *mbh;
   _EOE *eoembx;
   uint16_t frameinfo1;

   if (ESCvar.MBXrun == 0)
   {
      return;
   }
   if (!ESCvar.xoe && (MBXcontrol[0].state == MBXstate_inclaim))
   {
      mbh = (_MBXh *) &MBX[0];
      if (mbh->mbxtype == MBXEOE)
      {
         ESCvar.xoe = MBXEOE;
      }
   }
   if (ESCvar.xoe == MBXEOE)
   {
      eoembx = (_EOE *) &MBX[0];
      /* Verify the size of the file data. */
      if (etohs (eoembx->mbxheader.length) < ESC_EOEHSIZE)
      {
         EOE_no_data_response (
               EOE_INIT_RESP | EOE_HDR_LAST_FRAGMENT,
               MBXERR_SIZETOOSHORT);
      }
      else
      {
         frameinfo1 = etohs(eoembx->eoeheader.frameinfo1);
         switch (EOE_HDR_FRAME_TYPE_GET(frameinfo1))
         {
            case EOE_FRAG_DATA:
            {
               EOE_receive_fragment ();
               break;
            }
            case EOE_INIT_REQ:
            {
               EOE_set_ip ();
               break;
            }
            case EOE_GET_IP_PARAM_REQ:
            {
               EOE_get_ip ();
               break;
            }
            case EOE_INIT_RESP_TIMESTAMP:
            case EOE_INIT_RESP:
            case EOE_SET_ADDR_FILTER_REQ:
            case EOE_SET_ADDR_FILTER_RESP:
            case EOE_GET_IP_PARAM_RESP:
            case EOE_GET_ADDR_FILTER_REQ:
            case EOE_GET_ADDR_FILTER_RESP:
            default:
            {
               DPRINT("EOE_RESULT_UNSUPPORTED_TYPE\n");
               EOE_no_data_response ((EOE_HDR_FRAME_PORT & frameinfo1) |
                     (EOE_HDR_FRAME_TYPE & frameinfo1) |
                     EOE_HDR_LAST_FRAGMENT,
                     EOE_RESULT_UNSUPPORTED_FRAME_TYPE);
               break;
            }
         }
      }
      MBXcontrol[0].state = MBXstate_idle;
      ESCvar.xoe = 0;
   }
}
/** EoE function to send a fragment.
 * NOTE: Not thread safe, should be called from the SOES task sequential
 * with other mailbox functions. Add support for threading by adding
 * a thread safe application fetch function, example a mailbox with buffers
 * to send, posted by TCP/IP stack and fetched by SOES task.
 */
void ESC_eoeprocess_tx (void)
{
   if (ESCvar.MBXrun == 0)
   {
      return;
   }
   EOE_send_fragment ();
}
