/******************************************************************************
 * Copyright (c) 2004, 2008 IBM Corporation
 * All rights reserved.
 * This program and the accompanying materials
 * are made available under the terms of the BSD License
 * which accompanies this distribution, and is available at
 * http://www.opensource.org/licenses/bsd-license.php
 *
 * Contributors:
 *     IBM Corporation - initial implementation
 *****************************************************************************/


/******************************* ALGORITHMS ******************************/

/** \file netbase.c <pre>
 * *********************** Receive-handle diagram *************************
 *
 * Note: Every layer calls out required upper layer
 *
 * lower
 *  | MAC/LLC     Receive packet (receive_ether)
 *  |                           |
 *  | NETWORK       +-----------+---------+
 *  |               |                     |
 *  |           IPv4 (handle_ipv4)    IPv6 (handle_ipv4)
 *  |           ARP  (handle_arp)     ICMP & NDP
 *  |           ICMP                      |
 *  |                 |                   |
 *  |                 +---------+---------+
 *  |                           |
 *  | TRANSPORT       +---------+---------+
 *  |                 |                   |
 *  |              TCP (handle_tcp)    UDP (handle_udp)
 *  |                                     |
 *  | APPLICATION        +----------------+-----------+
 *  V                    |                            |
 * upper               DNS (handle_dns)      BootP / DHCP (handle_bootp_client)
 *
 * ************************************************************************
 * </pre> */


/************************ DEFINITIONS & DECLARATIONS *********************/

#include <ethernet.h>
#include <string.h>
#include <sys/socket.h>
#include <ipv4.h>
#include <ipv6.h>


/****************************** LOCAL VARIABLES **************************/

static uint8_t ether_packet[ETH_MTU_SIZE];
static uint8_t own_mac[6] = {0, 0, 0, 0, 0, 0};
static uint8_t multicast_mac[] = {0x01, 0x00, 0x5E};
static const uint8_t broadcast_mac[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};

/****************************** IMPLEMENTATION ***************************/

/**
 * Ethernet: Set the own MAC address to initializes ethernet layer.
 *
 * @param  own_mac  own hardware-address (MAC)
 */
void set_mac_address(const uint8_t * _own_mac)
{
	if (_own_mac)
		memcpy(own_mac, _own_mac, 6);
	else
		memset(own_mac, 0, 6);
}

/**
 * Ethernet: Set the own MAC address to initializes ethernet layer.
 *
 * @return  own hardware-address (MAC)
 */
const uint8_t *get_mac_address(void)
{
	return own_mac;
}

/**
 * Ethernet: Check if given multicast address is a multicast MAC address
 *           starting with 0x3333
 *
 * @return  true or false
 */
static uint8_t is_multicast_mac(uint8_t * mac)
{

    	uint16_t mc = 0x3333;
    	if (memcmp(mac, &mc, 2) == 0)
	       return 1;

	return 0;
}

/**
 * Ethernet: Receives an ethernet-packet and handles it according to
 *      Receive-handle diagram.
 *
 * @param  fd        socket fd
 * @return  ZERO - packet was handled or no packets received;
 *          NON ZERO - error condition occurs.
 */
int32_t receive_ether(int fd)
{
	int32_t bytes_received;
	struct ethhdr * ethh;

	memset(ether_packet, 0, ETH_MTU_SIZE);
	bytes_received = recv(fd, ether_packet, ETH_MTU_SIZE, 0);

	if (!bytes_received) // No messages
		return 0;

	if (bytes_received < 0)
		return -1; /* recv() failed */

	if ((size_t) bytes_received < sizeof(struct ethhdr))
		return -1; // packet is too small

	ethh = (struct ethhdr *) ether_packet;

	if(memcmp(ethh->dest_mac, broadcast_mac, 6) != 0
	&& memcmp(ethh->dest_mac, multicast_mac, 3) != 0
	&& memcmp(ethh->dest_mac, own_mac, 6      ) != 0
	&& !is_multicast_mac(ethh->dest_mac))
		return -1; // packet is too small

	switch (htons(ethh -> type)) {
	case ETHERTYPE_IP:
		return handle_ipv4(fd, (uint8_t*) (ethh + 1),
		                   bytes_received - sizeof(struct ethhdr));

	case ETHERTYPE_IPv6:
		return handle_ipv6(fd, ether_packet + sizeof(struct ethhdr),
				bytes_received - sizeof(struct ethhdr));

	case ETHERTYPE_ARP:
		return handle_arp(fd, (uint8_t*) (ethh + 1),
		           bytes_received - sizeof(struct ethhdr));
	default:
		break;
	}
	return -1; // unknown protocol
}

/**
 * Ethernet: Sends an ethernet frame via the initialized file descriptor.
 *
 * @return number of transmitted bytes
 */
int
send_ether(int fd, void* buffer, int len)
{
	return send(fd, buffer, len, 0);
}

/**
 * Ethernet: Creates Ethernet-packet. Places Ethernet-header in a packet and
 *           fills it with corresponding information.
 *           <p>
 *           Use this function with similar functions for other network layers
 *           (fill_arphdr, fill_iphdr, fill_udphdr, fill_dnshdr, fill_btphdr).
 *
 * @param  packet      Points to the place where eth-header must be placed.
 * @param  eth_type    Type of the next level protocol (e.g. IP or ARP).
 * @param  src_mac     Sender MAC address
 * @param  dest_mac    Receiver MAC address
 * @see                ethhdr
 * @see                fill_arphdr
 * @see                fill_iphdr
 * @see                fill_udphdr
 * @see                fill_dnshdr
 * @see                fill_btphdr
 */
void fill_ethhdr(uint8_t * packet, uint16_t eth_type,
		 const uint8_t * src_mac, const uint8_t * dest_mac)
{
	struct ethhdr * ethh = (struct ethhdr *) packet;

	ethh -> type = htons(eth_type);
	memcpy(ethh -> src_mac, src_mac, 6);
	memcpy(ethh -> dest_mac, dest_mac, 6);
}
