#include "archinc.h"
#include "cli.h"
#include "strutil.h"
#include "zarlink.h"
#include "icmp.h"
#include "ip.h"

#include <stdio.h>
#include <stdlib.h>

#ifdef CONFIG_INCLUDE_ZARLINK


static unsigned char arpReqFrameData[64];

/*
 * The structure of an ethernet header is: 
 * | Source MAC (6 bytes)  | Dest MAC (6 bytes) | type (2 bytes) |
 *
 * A type of 0x0800 indicates an IP packet
 *
 * The ethernet trailer is a 32-bit CRC
 */

static int initArpPacket(unsigned short opcode, char *pPktData, char *pSenderIp, char *pTargetIp)
{
  int i;
  arpRequest_t * pArpReq = (arpRequest_t *)(arpReqFrameData + 14);

  if (pPktData == NULL)
    return -1; 

  /*
   * Ethernet MAC layer data (offsets 0-13)
   */

  for(i=0;i<6;i++)
    arpReqFrameData[i] = 0xff; // broadcast

  zarlinkCpuMacAddrGet(&(arpReqFrameData[6]));
  // swapMac(&(arpReqFrameData[6]));

  // 0x0806 = ARP
  arpReqFrameData[12] = 0x08;
  arpReqFrameData[13] = 0x06;

  /*
   * ARP (ICMP) data (offsets 14-64)
   */

  pArpReq->hwType = byteSwap16(0x0001); // hwtype = ethernet
  pArpReq->protocolType = byteSwap16(0x0800); // prottype = IPv4 (0x0800)
  pArpReq->hwLen = 0x06;
  pArpReq->protocolLen = 0x04;

  // opcode = request (0x0001), reply (0x0002)
  pArpReq->operation = byteSwap16(opcode);

  if (pSenderIp != NULL) {
    zarlinkCpuMacAddrGet(&(pArpReq->senderEthMacAddr[0]));
    // swapMac(&(pArpReq->senderEthMacAddr[0]));
    for(i=0;i<4;i++) {
      pArpReq->senderIpAddr[i] = pSenderIp[i];
      }
    }
  else {
    for(i=0;i<4;i++) {
      pArpReq->senderIpAddr[i] = 0x00;
      }
    }

  if (pTargetIp != NULL) {
    zarlinkCpuMacAddrGet(&(pArpReq->targetEthMacAddr[0]));
    swapMac(&(pArpReq->targetEthMacAddr[0]));
    for(i=0;i<4;i++) {
      pArpReq->targetIpAddr[i] = pTargetIp[i];
      }
    }
  else {
    for(i=0;i<4;i++) {
      pArpReq->targetIpAddr[i] = 0x00;
      }
    }

  return 0;
}

static int initArpRequest(char *pPktData, char *pArpIp)
{
  unsigned char myIp[4];

  ipAddrGet(myIp);
  return initArpPacket((unsigned short)0x0001, pPktData, myIp, pArpIp);
}

static int initArpReply(char *pPktData, char *pArpIp)
{
  return initArpPacket((unsigned short)0x0002, pPktData, NULL, pArpIp);
}

int icmpArpRequestSend(char * pArpReqIpAddr, int port) 
{
  if (pArpReqIpAddr == NULL)
    return -1;

  if (initArpRequest(arpReqFrameData, pArpReqIpAddr) == 0) {
    return zarlinkEthFrameWrite(64, arpReqFrameData, port); 
    }

  return -1; // shouldn't get here
}

int icmpArpReplySend(char * pArpReplyIpAddr, int port) 
{
  if (pArpReplyIpAddr == NULL)
    return -1;

  if (initArpReply(arpReqFrameData, pArpReplyIpAddr) == 0) {
    return zarlinkEthFrameWrite(64, arpReqFrameData, port); 
    }

  return -1; // shouldn't get here
}

static unsigned char arpIp[4];

static int sendArpHandler(int argc, char * argv[], int flags)
{
  int retVal = -1;
  int sendPort;
  char * pOutBuf;
  int bufLen;

  if (argc < 4)
    return retVal;

  pOutBuf = clicharGetOutputBuffer(&bufLen);

  sendPort = (int)strtol(argv[2], (char **)NULL, 16);

  if (parseDottedQuadV4(argv[3], arpIp) == 0) {
    switch(*argv[1]) {
      case 'q':
      case 'Q':
        sprintf(pOutBuf, "%s: sending arp request on port [%02x] for IP %02d %02d %02d %02d\r\n", 
          argv[0], sendPort, arpIp[0], arpIp[1], arpIp[2], arpIp[3]); 
        clicharSendOutputBuffer(pOutBuf, bufLen);
        retVal = icmpArpRequestSend(arpIp, sendPort);
        break;
      case 'p':
      case 'P':
      case 'g':
      case 'G':
        sprintf(pOutBuf, "%s: sending arp reply (grat) on port [%02x] for IP %02d %02d %02d %02d\r\n", 
          argv[0], sendPort, arpIp[0], arpIp[1], arpIp[2], arpIp[3]); 
        clicharSendOutputBuffer(pOutBuf, bufLen);
        retVal = icmpArpReplySend(arpIp, sendPort);
        break;
      default:
        break;
      } // switch
    }

  return retVal;
}


int icmpInit(void)
{
  cliRegisterCommand("arp", sendArpHandler, "arp [re(q)uest | re(p)ly, (g)rat] [port] [IP Addr]");
  return 0;
}  

#endif /* CONFIG_INCLUDE_ZARLINK */

