/*
 * Copyright 2015 Mario Richter, Peter Stephan
 * This file is part of NeMo.
 *
 * NeMo is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * NeMo is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with NeMo.  If not, see <http://www.gnu.org/licenses/>.
 */


/*!
 * \file net.c
 * \ingroup	net
 * \brief	Netzwerk-Routinen zur IP-Kommunikation.
 */

/*!
 * \defgroup net TCP-/IP-Kommunikation
 * \ingroup net
 * {
 * 		\defgroup ip Routinen zur IP-Kommunikation
 * 		\defgroup tcp Routinen zur TCP-Kommunikation
 * 		\defgroup arp Routinen zum ARP-Handling
 * 		\defgroup icmp Routinen zum ICMP-Handling (Ping)
 * }
 */

#include "main.h"

pktType       pack;
arpTableType  arpTable[ARP_MAX_ENTRIES];

uint8_t       newPacketPresent;
uint8_t       myIpAddr[4];                  //!< Eigene IP-Adresse
uint8_t EEMEM eeIpAddr[4];                  //!< Eigene IP-Adresse (EEPROM)
uint16_t      ipIdCount;


/* %%%%%%%%%%%%%%%%%%%%%%%% IP-Packets %%%%%%%%%%%%%%%%%%%%%%%%% */

/*!
 * \ingroup ip
 * \brief Initialisierung des Netzwerk-Stacks.
 *
 * Diese Funktion initialisiert den Stack für die Netzwerkkommunikation. 
 */

void ipInitStack(void)
{
  uint8_t i = 0;

  // Lese IP-Adresse aus dem EEPROM
  for(i = 0; i < 4; i++)
  {
    myIpAddr[i] = eeprom_read_byte(&eeIpAddr[i]);       // Adressbyte holen
  }

  // Wenn alle Stellen 0 oder 255 sind, dann ist da was faul ...
  if( ((myIpAddr[0] == 0)   && (myIpAddr[1] == 0)   && (myIpAddr[2] == 0)   && (myIpAddr[3] == 0)) ||
      ((myIpAddr[0] == 255) && (myIpAddr[1] == 255) && (myIpAddr[2] == 255) && (myIpAddr[3] == 255)))
  {
    myIpAddr[0] = MYIP_0;
    myIpAddr[1] = MYIP_1;
    myIpAddr[2] = MYIP_2;
    myIpAddr[3] = MYIP_3;
    usart_send_line("Use default IP!");
    eeprom_write_block(myIpAddr, eeIpAddr, 4);          // Standard-IP ins EEPROM speichern
  }

  for(i = 0; i < TCP_MAX_CONNECTIONS; i++)              // Einträge in Verbindungsliste initialisieren
  {
    tcpConnList[i].status = TCP_STAT_CLOSED;
  }

  ipIdCount = 0;
}


/*!
 * \ingroup ip
 * \brief Zentraler Packet-Handler und Scheduler.
 *
 * Diese Funktion ist der zentrale Packet-Handler und Scheduler, welcher zyklisch aus der
 * main loop aufgerufen wird. Es wird geprüft, ob ein neues Packet eingetroffen ist, falls ja
 * wird dieses abgeholt, geprüft und weiterverteilt an die jeweiligen Protokollhandler.
 */

void ipHandlePacket(void)
{
	char buf[16];

  if(newPacketPresent)																// Ist ein neues Packet eingetroffen? (Signalisiert über Interrupt)
	{
		while(ETH_INTERRUPT_ACTIVE)												// Hole solange Packets ab, bis keine weiteren mehr vorhanden sind
		{
			if(enc28GetPacket(pack.buf) > 0)  					  // Hole Packet und prüfe, ob es Nutzdaten enthält
			{
        // ----------------------------------------------------------------------
        // IPv4-Packet

        if(HTONS(pack.pkt.protType) == ETH_PACKET_TYPE_IPV4)
        {
          if(pack.pkt.ipArp.ip.ipProtType == IP_PACKET_TYPE_ICMP)
          {
            arpRefreshEntry(&pack);
            icmpHandlePacket(&pack);
          }

          if(pack.pkt.ipArp.ip.ipProtType == IP_PACKET_TYPE_TCP)
          {
            arpRefreshEntry(&pack);
            tcpHandlePacket(&pack);
          }
        }

        // ----------------------------------------------------------------------
        // ARP-Packet

        else if(HTONS(pack.pkt.protType) == ETH_PACKET_TYPE_ARP)
        {
          if(HTONS(pack.pkt.ipArp.arp.operation) == ARP_OP_REPLY)
          {
            if(debugLevel & DEBUGLVL_STACK_INFO)
            {
              usart_send_string_P(PSTR("ARP Reply from "));
              snprintf(buf, 16, "%d.%d.%d.%d",
                       pack.pkt.ipArp.arp.sourceIp[0],
                       pack.pkt.ipArp.arp.sourceIp[1],
                       pack.pkt.ipArp.arp.sourceIp[2],
                       pack.pkt.ipArp.arp.sourceIp[3]);
              usart_send_line(buf);
            }

            arpRefreshEntry(&pack);
          }
          else if(HTONS(pack.pkt.ipArp.arp.operation) == ARP_OP_REQUEST)
          {
            arpRefreshEntry(&pack);
            arpReply(&pack);
          }
          else
          {
            if(debugLevel & DEBUGLVL_STACK_INFO)
            {
              usart_send_string_P(PSTR("Unknown ARP Type 0x"));
              usart_send_hexnumber((HTONS(pack.pkt.ipArp.arp.operation) & 0xFF00) >> 8, 1);
              usart_send_hexnumber((HTONS(pack.pkt.ipArp.arp.operation) & 0x00FF), 1);
              usart_send_line("");
            }
          }
        }

        // ----------------------------------------------------------------------
        // Unbekannter Packet-Typ

        else
        {
          if(debugLevel & DEBUGLVL_STACK_INFO)
          {
            usart_send_string_P(PSTR("Unknown Packet Type 0x"));
            usart_send_hexnumber((uint8_t)(HTONS(pack.pkt.protType) >> 8), 1);
            usart_send_hexnumber((uint8_t)(HTONS(pack.pkt.protType) & 0x00FF), 1);
            usart_send_line("");
          }
        }
			}
		}

		newPacketPresent = 0;															// Merker für neue Packets löschen
		ETH_INTERRUPT_ENABLE();														// Interrupt für neue Packets wieder freigeben
	}
}


/* %%%%%%%%%%%%%%%%%%%%%%%% ARP-Packets %%%%%%%%%%%%%%%%%%%%%%%%% */

/*!
 * \ingroup arp
 * \brief ARP-Tabelleneintrag updaten.
 *
 * Mit dieser Funktion wird der zu einem eintreffenden Packet zugehörige Datensatz (Pärchen aus
 * MAC- und IP-Adresse) in der ARP-Tabelle aufgefrischt (Reset des Lebensdauer-Counters) bzw. neu
 * eingetragen, falls noch nicht vorhanden. Die MAC- und IP-Adresse werden vom im Packet eingetragenen
 * Sender übernommen. Dabei ist es egal, ob das eintreffende Packet ein ARP-Reply oder ein IP-Packet ist.
 *
 * \param pkt Zeiger auf das Packet, dessen Sender in die ARP-Tabelle eingetragen werden soll.
 * \sa arpReply, arpTimer
 */

void arpRefreshEntry(pktType * packet)
{
  uint8_t entryFound = 0;
  uint8_t i = 0;

  // ----------------------------------------------------------------------
  // Prüfe, ob Eintrag mit Sender-IP-Adresse bereits vorhanden ist -> Updaten

  for(i = 0; (i < ARP_MAX_ENTRIES) && (entryFound == 0); i++)
  {
    if((HTONS(packet->pkt.protType) == ETH_PACKET_TYPE_ARP) &&          // ARP-Packet: Vergleiche IP-Adressen
        (packet->pkt.ipArp.arp.sourceIp[0] == arpTable[i].ip[0]) &&
        (packet->pkt.ipArp.arp.sourceIp[1] == arpTable[i].ip[1]) &&
        (packet->pkt.ipArp.arp.sourceIp[2] == arpTable[i].ip[2]) &&
        (packet->pkt.ipArp.arp.sourceIp[3] == arpTable[i].ip[3]))
    {
      arpTable[i].timer = ARP_ENTRY_LIFETIME;
      entryFound = 1;
    }

    if((HTONS(packet->pkt.protType) == ETH_PACKET_TYPE_IPV4) &&         // IPv4-Packet: Vergleiche IP-Adressen
        (packet->pkt.ipArp.ip.ipSourceIp[0] == arpTable[i].ip[0]) &&
        (packet->pkt.ipArp.ip.ipSourceIp[1] == arpTable[i].ip[1]) &&
        (packet->pkt.ipArp.ip.ipSourceIp[2] == arpTable[i].ip[2]) &&
        (packet->pkt.ipArp.ip.ipSourceIp[3] == arpTable[i].ip[3]))
    {
      arpTable[i].timer = ARP_ENTRY_LIFETIME;
      entryFound = 1;
    }
  }

  if(!entryFound)
  {
    // ----------------------------------------------------------------------
    // IP-Adresse noch unbekannt: Suche freien Eintrag in der ARP-Tabelle

    for(i = 0; (i < ARP_MAX_ENTRIES) && (entryFound == 0); i++)
    {
      if( (arpTable[i].ip[0] == 0) &&                                   // Ist Eintrag leer?
          (arpTable[i].ip[1] == 0) &&
          (arpTable[i].ip[2] == 0) &&
          (arpTable[i].ip[3] == 0))
      {
        if(HTONS(packet->pkt.protType) == ETH_PACKET_TYPE_ARP)          // Daten aus ARP-Packet eintragen
        {
          arpTable[i].ip[0] = packet->pkt.ipArp.arp.sourceIp[0];
          arpTable[i].ip[1] = packet->pkt.ipArp.arp.sourceIp[1];
          arpTable[i].ip[2] = packet->pkt.ipArp.arp.sourceIp[2];
          arpTable[i].ip[3] = packet->pkt.ipArp.arp.sourceIp[3];
          arpTable[i].mac[0] = packet->pkt.ipArp.arp.sourceMac[0];
          arpTable[i].mac[1] = packet->pkt.ipArp.arp.sourceMac[1];
          arpTable[i].mac[2] = packet->pkt.ipArp.arp.sourceMac[2];
          arpTable[i].mac[3] = packet->pkt.ipArp.arp.sourceMac[3];
          arpTable[i].mac[4] = packet->pkt.ipArp.arp.sourceMac[4];
          arpTable[i].mac[5] = packet->pkt.ipArp.arp.sourceMac[5];
          arpTable[i].timer = ARP_ENTRY_LIFETIME;
          entryFound = 1;
        }

        if(HTONS(packet->pkt.protType) == ETH_PACKET_TYPE_IPV4)         // Daten aus IPv4-Packet eintragen
        {
          arpTable[i].ip[0] = packet->pkt.ipArp.ip.ipSourceIp[0];
          arpTable[i].ip[1] = packet->pkt.ipArp.ip.ipSourceIp[1];
          arpTable[i].ip[2] = packet->pkt.ipArp.ip.ipSourceIp[2];
          arpTable[i].ip[3] = packet->pkt.ipArp.ip.ipSourceIp[3];
          arpTable[i].mac[0] = packet->pkt.sourceMac[0];
          arpTable[i].mac[1] = packet->pkt.sourceMac[1];
          arpTable[i].mac[2] = packet->pkt.sourceMac[2];
          arpTable[i].mac[3] = packet->pkt.sourceMac[3];
          arpTable[i].mac[4] = packet->pkt.sourceMac[4];
          arpTable[i].mac[5] = packet->pkt.sourceMac[5];
          arpTable[i].timer = ARP_ENTRY_LIFETIME;
          entryFound = 1;
        }
      }
    }
  }

  // ----------------------------------------------------------------------
  // Noch immer kein Eintrag: IP-Adresse neu, aber kein Platz mehr in Tabelle frei

  if((!entryFound) && (debugLevel & DEBUGLVL_STACK_ERROR))
  {
    usart_send_line_P(PSTR("ARP Table full!"));
  }
}


/*!
 * \ingroup arp
 * \brief ARP-Request senden.
 *
 * Diese Funktion sendet einen ARP-Request, wenn zu einer IP-Adresse die zugehörige, unbekannte
 * MAC-Adresse ermittelt werden soll. Die Routine setzt nur die Anfrage ab, sie wartet jedoch
 * nicht auf eine Antwort. Eine entsprechende Funktionalität muss in der darüberliegenden
 * Schicht abgebildet werden.
 *
 * \param ipAddr Zeiger auf die IP-Adresse, deren zugehörige MAC ermittelt werden soll.
 * \sa arpReply, arpTimer
 */

void arpRequest(uint8_t * ipAddr)
{
  uint8_t i;
  char buf[16];

  if(debugLevel & DEBUGLVL_STACK_INFO)
  {
    usart_send_string_P(PSTR("ARP Request to "));
    snprintf(buf, 16, "%d.%d.%d.%d", *ipAddr, *(ipAddr+1), *(ipAddr+2), *(ipAddr+3));
    usart_send_line(buf);
  }

  // Ethernet Protokoll-Typ
  pack.pkt.protType = HTONS(ETH_PACKET_TYPE_ARP);

  // Packet vorbereiten: Setze Quell-MAC und Ziel-MAC
  for(i = 0; i < 6; i++)
  {
    // Ethernet Header: MAC-Adressen
    pack.pkt.destinationMac[i] = 0xFF;       // Broadcast
    pack.pkt.sourceMac[i] = myMacAddr[i];

    // ARP-Packet: MAC-Adressen
    pack.pkt.ipArp.arp.destMac[i] = 0xFF;    // Broadcast
    pack.pkt.ipArp.arp.sourceMac[i] = myMacAddr[i];
  }

  // Setze Quell- und Ziel-IP-Adressen
  for(i = 0; i < 4; i++)
  {
    // ARP-Packet: IP-Adressen
    pack.pkt.ipArp.arp.destIp[i] = *(ipAddr + i);
    pack.pkt.ipArp.arp.sourceIp[i] = myIpAddr[i];
  }

  // Operation Type: ARP Request
  pack.pkt.ipArp.arp.operation = HTONS(ARP_OP_REQUEST);

  // Größeninformationen
  pack.pkt.ipArp.arp.hwAddrSize = 0x06;                // HW-Adressgröße (6)
  pack.pkt.ipArp.arp.hwAddrType = HTONS(0x0001);       // HW-Adresstyp (0x0001)
  pack.pkt.ipArp.arp.protAddrSize = 0x04;              // Protokoll-Adressgröße (4)
  pack.pkt.ipArp.arp.protAddrType = HTONS(0x0800);     // Protokoll-Adresstyp (0x0800)

  // Paket senden
  enc28SendPacket(pack.buf, ARP_REPLY_LENGTH);
}


/*!
 * \ingroup arp
 * \brief ARP-Reply senden.
 *
 * In dieser Funktion wird ein empfangener ARP-Request beantwortet. Die Funktion erstellt ein
 * ARP-Reply-Packet und gibt somit die eigene IP- und MAC-Adresse bekannt. 
 *
 * \param pkt Zeiger auf das Packet, das den eingegangenen Request enthält.
 * \sa arpRefreshEntry, arpTimer
 */

void arpReply(pktType * packet)
{
  uint8_t i;
  char buf[16];

  // Prüfe, ob ARP-Packet korrekt ist
  if( (packet->pkt.ipArp.arp.hwAddrSize == 0x06) &&             // HW-Adressgröße (6)
      (packet->pkt.ipArp.arp.hwAddrType == HTONS(0x0001)) &&    // HW-Adresstyp (0x0001)
      (packet->pkt.ipArp.arp.protAddrSize == 0x04) &&           // Protokoll-Adressgröße (4)
      (packet->pkt.ipArp.arp.protAddrType == HTONS(0x0800)) &&  // Protokoll-Adresstyp (0x0800)
      (packet->pkt.ipArp.arp.destIp[0] == myIpAddr[0]) &&       // Sind wir gemeint?
      (packet->pkt.ipArp.arp.destIp[1] == myIpAddr[1]) &&
      (packet->pkt.ipArp.arp.destIp[2] == myIpAddr[2]) &&
      (packet->pkt.ipArp.arp.destIp[3] == myIpAddr[3]))
  {
    if(debugLevel & DEBUGLVL_STACK_INFO)
    {
      usart_send_string_P(PSTR("ARP Reply to "));
      snprintf(buf, 16, "%d.%d.%d.%d",
               packet->pkt.ipArp.arp.sourceIp[0],
               packet->pkt.ipArp.arp.sourceIp[1],
               packet->pkt.ipArp.arp.sourceIp[2],
               packet->pkt.ipArp.arp.sourceIp[3]);
      usart_send_line(buf);
    }

    // Ethernet Protokoll-Typ
    pack.pkt.protType = HTONS(ETH_PACKET_TYPE_ARP);

    // Antwort-Packet vorbereiten: Setze Quell-MAC und Ziel-MAC
    for(i = 0; i < 6; i++)
    {
      // Ethernet Header: MAC-Adressen
      packet->pkt.destinationMac[i] = packet->pkt.sourceMac[i];
      packet->pkt.sourceMac[i] = myMacAddr[i];

      // ARP-Packet: MAC-Adressen
      packet->pkt.ipArp.arp.destMac[i] = packet->pkt.ipArp.arp.sourceMac[i];
      packet->pkt.ipArp.arp.sourceMac[i] = myMacAddr[i];
    }

    // Setze Quell- und Ziel-IP-Adressen
    for(i = 0; i < 4; i++)
    {
      // ARP-Packet: IP-Adressen
      packet->pkt.ipArp.arp.destIp[i] = packet->pkt.ipArp.arp.sourceIp[i];
      packet->pkt.ipArp.arp.sourceIp[i] = myIpAddr[i];
    }

    // Operation Type: ARP Reply
    packet->pkt.ipArp.arp.operation = HTONS(ARP_OP_REPLY);

    // Antwort senden
    enc28SendPacket(packet->buf, ARP_REPLY_LENGTH);
  }
}


/*!
 * \ingroup arp
 * \brief ARP-Timer-Routine: Lebensdauerzähler führen.
 *
 * Diese Funktion aktualisiert die Lebensdauerzähler in der ARP-Tabelle. Sie wird sekündlich
 * aufgerufen und dekrementiert alle Zähler größer 0. Wird ein Eintrag 0, wird dieser gelöscht.
 *
 * \sa arpReply, arpRequest
 */

void arpTimer(void)
{
  uint8_t i, j;

  for(i = 0; i < ARP_MAX_ENTRIES; i++)
  {
    if(arpTable[i].timer > 0)               // Ist Eintrag noch aktuell?
    {
      arpTable[i].timer--;                  // Lebensdauer runterzählen

      if(arpTable[i].timer == 0)            // Ist Eintrag jetzt veraltet?
      {
        for(j = 0; j < 6; j++)              // MAC- und IP-Adresse löschen
          arpTable[i].mac[j] = 0;

        for(j = 0; j < 4; j++)
          arpTable[i].ip[j] = 0;
      }
    }
  }
}


/*!
 * \ingroup arp
 * \brief MAC-Adresse zu IP-Adresse aus ARP-Tabelle holen
 *
 * Diese Funktion sucht zu einer bekannten IP-Adresse die zugehörige MAC-Adresse in der
 * ARP-Tabelle und gibt einen Zeiger auf diese zurück. Wird die IP-Adresse nicht gefunden,
 * wird NULL zurückgeliefert.
 *
 * \sa arpReply, arpRequest
 */

uint8_t * arpGetMac(uint8_t * ip)
{
  uint8_t i;
  uint8_t * ptr = NULL;

  // Durchsuche ARP-Tabelle nach passender IP
  for(i = 0; (i < ARP_MAX_ENTRIES) && (ptr == NULL); i++)
  {
    if( (ip[0] == arpTable[i].ip[0]) &&
        (ip[1] == arpTable[i].ip[1]) &&
        (ip[2] == arpTable[i].ip[2]) &&
        (ip[3] == arpTable[i].ip[3]))
    {
      ptr = arpTable[i].mac;
    }
  }

  return ptr;
}


/*!
 * \ingroup arp
 * \brief ARP-Tabelle ausgeben.
 *
 * Diese Funktion gibt die ARP-Tabelle auf der seriellen Schnittstelle aus.
 */

void arpTableShow(uint8_t ind)
{
  uint8_t i, j;
  char buf[6];
  sendStringP(ind, PSTR(" #  MAC                IP               Timer\r\n"));

  for(i = 0; i < ARP_MAX_ENTRIES; i++)
  {
    if(arpTable[i].timer > 0)
    {
      sendChar(ind, ' ');
      sendChar(ind, i + 0x31);      // Erster Eintrag: i=0, Addition 0x31 ergibt '1' usw.
      sendChar(ind, ' ');
      sendChar(ind, ' ');

      for(j = 0; j < 6; j++)          // MAC-Adresse ausgeben
      {
        sendHex(ind, arpTable[i].mac[j], 1);
        sendChar(ind, (j < 5) ? ':' : ' ');
      }

      sendChar(ind, ' ');

      for(j = 0; j < 4; j++)          // IP-Adresse ausgeben
      {
        snprintf(buf, 4, "%03d", arpTable[i].ip[j]);
        sendString(ind, buf);
        sendChar(ind, (j < 3) ? '.' : ' ');
      }

      sendChar(ind, ' ');

      // Timer-Wert ausgeben
      snprintf(buf, 6, "%5d", arpTable[i].timer);
      sendString(ind, buf);
      sendLine(ind, "");
    }
  }
}


/*!
 * \ingroup arp
 * \brief ARP-Tabelle löschen.
 *
 * Diese Funktion löscht alle Einträge der ARP-Table.
 */

void arpTableClear(void)
{
  uint8_t i = 0;
    
  for(i = 0; i < ARP_MAX_ENTRIES; i++)
  {
    arpTable[i].ip[0] = 0;
    arpTable[i].ip[1] = 0;
    arpTable[i].ip[2] = 0;
    arpTable[i].ip[3] = 0;
    arpTable[i].timer = 0;
  }
}

/* %%%%%%%%%%%%%%%%%%%%%%%% ICMP-Packets %%%%%%%%%%%%%%%%%%%%%%%%% */

/*!
 * \ingroup icmp
 * \brief Behandlung von ICMP-Paketen (Ping)
 *
 * Diese Funktion behandelt eingetroffene ICMP-Pakete, mit denen ein Ping des eigenen oder von anderen
 * Geräten im Netzwerk durchgeführt werden kann. Hierbei wird unterschieden, ob es sich um eine Antwort
 * auf eine eigene Ping-Anfrage oder um einen Ping-Request von einem anderen Teilnehmer handelt.
 *
 * \param packet Zeiger auf den Packetbuffer mit dem eingegangenen ICMP-Packet.
 */

void icmpHandlePacket(pktType * packet)
{
  uint8_t i;
  char buf[6];

  // Echo-Anfrage eines anderen Teilnehmers
  if(packet->pkt.ipArp.ip.data.icmp.type == ICMP_ECHO_REQUEST)
  {
    // Ethernet-Header MAC-Adressen setzen
    for(i = 0; i < 6; i++)
    {
      packet->pkt.destinationMac[i] = packet->pkt.sourceMac[i];
      packet->pkt.sourceMac[i] = myMacAddr[i];
    }

    // IP-Header zusammensetzen
    packet->pkt.ipArp.ip.ipVerIhl = 0x45;                   // IP Version 4, Headerlänge 5 Byte
    packet->pkt.ipArp.ip.ipTos = 0;                         // IP Type of Service

    ipIdCount++;
    packet->pkt.ipArp.ip.ipId = HTONS(ipIdCount);           // Packet ID Counter
    packet->pkt.ipArp.ip.ipFlagsOffset = HTONS(0x4000);     // DF-Flag (do not fragment)
    packet->pkt.ipArp.ip.ipTtl = IP_MAX_TTL;                // TTL standardmäßig setzen
    packet->pkt.ipArp.ip.ipProtType = IP_PACKET_TYPE_ICMP;  // ICMP-Folgeprotokoll
    packet->pkt.ipArp.ip.ipChecksum = 0;                    // wird gleich noch berechnet

    // IP-Adressen setzen
    for(i = 0; i < 4; i++)
    {
      packet->pkt.ipArp.ip.ipDestIp[i] = packet->pkt.ipArp.ip.ipSourceIp[i];
      packet->pkt.ipArp.ip.ipSourceIp[i] = myIpAddr[i];
    }

    // Checksumme für IP-Header berechnen
    packet->pkt.ipArp.ip.ipChecksum = HTONS(calcChecksum(&packet->pkt.ipArp.ip.ipVerIhl, 20));

    // ICMP Echo Antwort-Paket zusammenbauen
    packet->pkt.ipArp.ip.data.icmp.code = 0;                // Echo Antwort: Code = 0 und ...
    packet->pkt.ipArp.ip.data.icmp.type = 0;                // ... Type  = 0
    packet->pkt.ipArp.ip.data.icmp.checksum = 0;            // wird gleich noch berechnet

    // Checksumme des ICMP-Packets berechnen (Länge = Gesamtlänge des IP-Packets minus IP-Headerlänge (20))
    packet->pkt.ipArp.ip.data.icmp.checksum =
        HTONS(calcChecksum(&packet->pkt.ipArp.ip.data.icmp.type,
                           HTONS(packet->pkt.ipArp.ip.ipLength) - 20));

    // Antwort senden: IP-Paketlänge + 14 Bytes Ethernetheader (OHNE Ethernet-Checksumme!)
    enc28SendPacket(packet->buf, HTONS(packet->pkt.ipArp.ip.ipLength) + 14);

    if(debugLevel & DEBUGLVL_STACK_INFO)
    {
      usart_send_string_P(PSTR("Echo reply to "));
      for(i = 0; i < 4; i++)          // IP-Adresse ausgeben
      {
        snprintf(buf, 4, "%d", packet->pkt.ipArp.ip.ipDestIp[i]);
        usart_send_string(buf);
        usart_send_char((i < 3) ? '.' : ' ');
      }
      usart_send_line("");
    }
  }
}


/* %%%%%%%%%%%%%%%%%%%%%%%% SONSTIGES %%%%%%%%%%%%%%%%%%%%%%%%% */

/*!
 * \ingroup net
 * \brief Checksummenberechnung (16 Bit)
 *
 * Diese Funktion berechnet eine 16-Bit-Checksumme aus den angegebenen Daten und gibt diese zurück.
 * Dabei werden die Daten zunächst in 16-Bit-Pakete gestapelt und aufsummiert (bei ungerader Anzahl
 * von Bytes wandert das letzte Byte in das höhere Byte des Wortes). Anschließend wird aus dieser
 * Summe das Einerkomplement gebildet, das die Prüfsumme ergibt.
 *
 * \param buf Zeiger auf den Buffer mit den Daten, über die die Checksumme berechnet werden soll.
 * \param len Länge der Daten zur Berechnung der Checksumme (in Bytes).
 * \return Checksumme (16 Bit)
 */

uint16_t calcChecksum(uint8_t * buf, uint16_t len)
{
  uint32_t res32 = 0;
  uint16_t res16 = 0;

  while(len > 1)
  {
    res16  = (uint16_t)(*buf++) << 8;         // 16 Bit-Wort aus zwei Bytes bilden
    res16 += (uint16_t)(*buf++);
    res32 += res16;                           // Wort zu Summe hinzuaddieren
    len -= 2;                                 // Zwei Bytes wurden verarbeitet
  }

  if(len > 0)                                 // Bei ungerader Länge: Letztes Byte behandeln
  {
    res16  = (uint16_t)(*buf) << 8;           // Letztes Byte kommt in oberes Byte des Wortes
    res32 += res16;                           // Zu Summe hinzuaddieren
  }

  // Jetzt die einzelnen Worte der Summe 2x aufaddieren, so dass oberes Wort = 0 ist
  res32 = ((res32 & 0xFFFF0000) >> 16) + (res32 & 0x0000FFFF);
  res32 = ((res32 & 0xFFFF0000) >> 16) + (res32 & 0x0000FFFF);

  // Schließlich noch das Einerkomplement bilden
  res16 = ~(res32 & 0x0000FFFF);

  return res16;
}
