/**
 * @file ethernet.c
 * @brief Minimal lwIP Ethernet helper implementation.
 */

#include "lwip/opt.h"

#if LWIP_ARP || LWIP_ETHERNET

#include "netif/ethernet.h"

#include "lwip/def.h"
#include "lwip/ip.h"
#include "lwip/pbuf.h"
#include "lwip/stats.h"
#include "lwip/prot/ethernet.h"

#include <string.h>

#if LWIP_IPV4 && LWIP_ARP
#include "lwip/etharp.h"
#endif

#if LWIP_IPV6
#include "lwip/ip6.h"
#endif

const struct eth_addr ethbroadcast = ETH_ADDR(0xff, 0xff, 0xff, 0xff, 0xff, 0xff);
const struct eth_addr ethzero      = ETH_ADDR(0x00, 0x00, 0x00, 0x00, 0x00, 0x00);

err_t ethernet_output(struct netif *netif,
                      struct pbuf *p,
                      const struct eth_addr *src,
                      const struct eth_addr *dst,
                      u16_t eth_type)
{
  struct pbuf *q;
  struct eth_hdr *ethhdr;
  err_t err;

  LWIP_ASSERT("netif != NULL", netif != NULL);
  LWIP_ASSERT("p != NULL", p != NULL);
  LWIP_ASSERT("src != NULL", src != NULL);
  LWIP_ASSERT("dst != NULL", dst != NULL);

  q = pbuf_alloc(PBUF_RAW_TX, SIZEOF_ETH_HDR, PBUF_RAM);
  if (q == NULL) {
    LINK_STATS_INC(link.memerr);
    LINK_STATS_INC(link.drop);
    return ERR_MEM;
  }

  pbuf_chain(q, p);

  ethhdr = (struct eth_hdr *)q->payload;
  SMEMCPY(&ethhdr->dest, dst, sizeof(struct eth_addr));
  SMEMCPY(&ethhdr->src, src, sizeof(struct eth_addr));
  ethhdr->type = lwip_htons(eth_type);

  err = netif->linkoutput(netif, q);
  pbuf_free(q);
  return err;
}

err_t ethernet_input(struct pbuf *p, struct netif *netif)
{
  struct eth_hdr *ethhdr;
  u16_t type;

  LWIP_ASSERT("p != NULL", p != NULL);
  LWIP_ASSERT("netif != NULL", netif != NULL);

  if (p->len < SIZEOF_ETH_HDR) {
    LINK_STATS_INC(link.lenerr);
    LINK_STATS_INC(link.drop);
    pbuf_free(p);
    return ERR_OK;
  }

  ethhdr = (struct eth_hdr *)p->payload;
  type = lwip_htons(ethhdr->type);

  switch (type) {
#if LWIP_IPV4 && LWIP_ARP
    case ETHTYPE_ARP:
      if (netif->flags & NETIF_FLAG_ETHARP) {
        if (pbuf_remove_header(p, SIZEOF_ETH_HDR)) {
          pbuf_free(p);
          return ERR_OK;
        }
        etharp_input(p, netif);
      } else {
        pbuf_free(p);
      }
      return ERR_OK;

    case ETHTYPE_IP:
      if (netif->flags & (NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET)) {
        if (pbuf_remove_header(p, SIZEOF_ETH_HDR)) {
          pbuf_free(p);
          return ERR_OK;
        }
        return ip_input(p, netif);
      }
      break;
#endif

#if LWIP_IPV6
    case ETHTYPE_IPV6:
      if (netif->flags & NETIF_FLAG_ETHERNET) {
        if (pbuf_remove_header(p, SIZEOF_ETH_HDR)) {
          pbuf_free(p);
          return ERR_OK;
        }
        return ip6_input(p, netif);
      }
      break;
#endif

    default:
      break;
  }

  pbuf_free(p);
  return ERR_OK;
}

#endif /* LWIP_ARP || LWIP_ETHERNET */
