/******************************************************************************
 * 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 dhcp.c <pre>
 * **************** State-transition diagram for DHCP client  *************
 *
 *   +---------+                  Note: DHCP-server msg / DHCP-client msg
 *   |  INIT   |
 *   +---------+
 *        |
 *        |  - / Discover
 *        V
 *   +---------+
 *   | SELECT  |                     Timeout
 *   +---------+                        |
 *        |                             |
 *        |  Offer / Request            |
 *        |                             |
 *        V                             V
 *   +---------+     NACK / -      ***********
 *   | REQUEST | ----------------> *  FAULT  *
 *   +---------+                   ***********
 *        |
 *        |          ACK / -       ***********
 *        +----------------------> * SUCCESS *
 *                                 ***********
 *
 * ************************************************************************
 * </pre> */


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

#include <dhcp.h>
#include <ethernet.h>
#include <ipv4.h>
#include <udp.h>
#include <dns.h>
#include <args.h>

#include <stdio.h>
#include <string.h>
#include <time.h>
#include <sys/socket.h>
#include <ctype.h>
#include <stdlib.h>

/* DHCP Message Types */
#define DHCPDISCOVER    1
#define DHCPOFFER       2
#define DHCPREQUEST     3
#define DHCPDECLINE     4
#define DHCPACK	        5
#define DHCPNACK        6
#define DHCPRELEASE     7
#define DHCPINFORM      8

/* DHCP Option Codes */
#define DHCP_MASK              1
#define DHCP_ROUTER            3
#define DHCP_DNS               6
#define DHCP_REQUESTED_IP     50
#define DHCP_OVERLOAD         52
#define DHCP_MSG_TYPE         53
#define DHCP_SERVER_ID        54
#define DHCP_REQUEST_LIST     55
#define DHCP_TFTP_SERVER      66
#define DHCP_BOOTFILE         67
#define DHCP_CLIENT_ARCH      93
#define DHCP_PXELINUX_CFGFILE 209   /* See RFC 5071 */
#define DHCP_PXELINUX_PREFIX  210
#define DHCP_ENDOPT         0xFF
#define DHCP_PADOPT         0x00

/* "file/sname" overload option values */
#define DHCP_OVERLOAD_FILE     1
#define DHCP_OVERLOAD_SNAME    2
#define DHCP_OVERLOAD_BOTH     3

/* DHCP states codes */
#define DHCP_STATE_SELECT      1
#define DHCP_STATE_REQUEST     2
#define DHCP_STATE_SUCCESS     3
#define DHCP_STATE_FAULT       4

/* DHCP Client Architecture */
#ifndef DHCPARCH
#define USE_DHCPARCH 0
#define DHCPARCH 0
#else
#define USE_DHCPARCH 1
#endif

static uint8_t dhcp_magic[] = {0x63, 0x82, 0x53, 0x63};
/**< DHCP_magic is a cookie, that identifies DHCP options (see RFC 2132) */

/** \struct dhcp_options_t
 *  This structure is used to fill options in DHCP-msg during transmitting
 *  or to retrieve options from DHCP-msg during receiving.
 *  <p>
 *  If flag[i] == TRUE then field for i-th option retains valid value and
 *  information from this field may be retrieved (in case of receiving) or will
 *  be transmitted (in case of transmitting).
 *
 */
typedef struct {
	uint8_t    flag[256];         /**< Show if corresponding opt. is valid */
	uint8_t    request_list[256]; /**< o.55 If i-th member is TRUE, then i-th
	                                  option will be requested from server */
	uint32_t   server_ID;         /**< o.54 Identifies DHCP-server         */
	uint32_t   requested_IP;      /**< o.50 Must be filled in DHCP-Request */
	uint32_t   dns_IP;            /**< o. 6 DNS IP                         */
	uint32_t   router_IP;         /**< o. 3 Router IP                      */
	uint32_t   subnet_mask;       /**< o. 1 Subnet mask                    */
	uint8_t    msg_type;          /**< o.53 DHCP-message type              */
	uint8_t    overload;          /**< o.52 Overload sname/file fields     */
	char       tftp_server[256];  /**< o.66 TFTP server name               */
	char       bootfile[256];     /**< o.67 Boot file name                 */
	uint16_t   client_arch;       /**< o.93 Client architecture type       */
} dhcp_options_t;

/** Stores state of DHCP-client (refer to State-transition diagram) */
static uint8_t dhcp_state;


/***************************** PROTOTYPES ********************************/

static int32_t dhcp_attempt(int fd);

static int32_t dhcp_encode_options(uint8_t * opt_field, dhcp_options_t * opt_struct);

static int32_t dhcp_decode_options(uint8_t opt_field[], uint32_t opt_len,
				   dhcp_options_t * opt_struct);

static int8_t dhcp_merge_options(uint8_t dst_options[], uint32_t * dst_len,
				 uint8_t src_options[], uint32_t src_len);

static int8_t dhcp_find_option(uint8_t options[], uint32_t len,
			       uint8_t op_code, uint32_t * op_offset);

static void dhcp_append_option(uint8_t dst_options[], uint32_t * dst_len,
			       uint8_t * new_option);

static void dhcp_combine_option(uint8_t dst_options[], uint32_t * dst_len,
				uint32_t dst_offset, uint8_t * new_option);

static void dhcp_send_discover(int fd);

static void dhcp_send_request(int fd);

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

static uint8_t  ether_packet[ETH_MTU_SIZE];
static uint32_t dhcp_own_ip        = 0;
static uint32_t dhcp_server_ip     = 0;
static uint32_t dhcp_siaddr_ip     = 0;
static char   dhcp_filename[256];
static char   dhcp_tftp_name[256];
static uint32_t dhcp_xid;
static char *pxelinux_cfgfile;
static char *pxelinux_prefix;

static char   * response_buffer;

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

void dhcpv4_generate_transaction_id(void)
{
	dhcp_xid = (rand() << 16) ^ rand();
}

int32_t dhcpv4(char *ret_buffer, filename_ip_t *fn_ip)
{
	uint32_t dhcp_tftp_ip     = 0;
	int fd = fn_ip->fd;

	strcpy(dhcp_filename, "");
	strcpy(dhcp_tftp_name, "");

	pxelinux_cfgfile = pxelinux_prefix = NULL;

	response_buffer = ret_buffer;

	if (dhcp_attempt(fd) == 0)
		return -1;

	if (fn_ip->own_ip) {
		dhcp_own_ip = fn_ip->own_ip;
	}
	if (fn_ip->server_ip) {
		dhcp_siaddr_ip = fn_ip->server_ip;
	}
	if(fn_ip->filename[0] != 0) {
		strcpy(dhcp_filename, fn_ip->filename);
	}

	// TFTP SERVER
	if (!strlen(dhcp_tftp_name)) {
		if (!dhcp_siaddr_ip) {
			// ERROR: TFTP name is not presented
			return -3;
		}

		// take TFTP-ip from siaddr field
		dhcp_tftp_ip = dhcp_siaddr_ip;
	}
	else {
		// TFTP server defined by its name
		if (!strtoip(dhcp_tftp_name, (char *)&dhcp_tftp_ip)) {
			if (!dns_get_ip(fd, dhcp_tftp_name, (uint8_t *)&dhcp_tftp_ip, 4)) {
				// DNS error - can't obtain TFTP-server name
				// Use TFTP-ip from siaddr field, if presented
				if (dhcp_siaddr_ip) {
					dhcp_tftp_ip = dhcp_siaddr_ip;
				}
				else {
					// ERROR: Can't obtain TFTP server IP
					return -4;
				}
			}
		}
	}

	// Store configuration info into filename_ip structure
	fn_ip -> own_ip = dhcp_own_ip;
	fn_ip -> server_ip = dhcp_tftp_ip;
	strcpy(fn_ip->filename, dhcp_filename);

	fn_ip->pl_cfgfile = pxelinux_cfgfile;
	fn_ip->pl_prefix = pxelinux_prefix;
	pxelinux_cfgfile = pxelinux_prefix = NULL;

	return 0;
}

/**
 * DHCP: Tries o obtain DHCP parameters, refer to state-transition diagram
 */
static int32_t dhcp_attempt(int fd)
{
	int sec;

	// Send DISCOVER message and switch DHCP-client to SELECT state
	dhcp_send_discover(fd);

	dhcp_state = DHCP_STATE_SELECT;

	// setting up a timer with a timeout of two seconds
	for (sec = 0; sec < 2; sec++) {
		set_timer(TICKS_SEC);
		do {
			receive_ether(fd);

			// Wait until client will switch to Final state or Timeout occurs
			switch (dhcp_state) {
			case DHCP_STATE_SUCCESS :
				return 1;
			case DHCP_STATE_FAULT :
				return 0;
			}
		} while (get_timer() > 0);
	}

	// timeout
	return 0;
}

/**
 * DHCP: Supplements DHCP-message with options stored in structure.
 *       For more information about option coding see dhcp_options_t.
 *
 * @param  opt_field     Points to the "vend" field of DHCP-message
 *                       (destination)
 * @param  opt_struct    this structure stores info about the options which
 *                       will be added to DHCP-message (source)
 * @return               TRUE - options packed;
 *                       FALSE - error condition occurs.
 * @see                  dhcp_options_t
 */
static int32_t dhcp_encode_options(uint8_t * opt_field, dhcp_options_t * opt_struct)
{
	uint8_t * options = opt_field;
	uint16_t i, sum; // used to define is any options set

	// magic
	memcpy(options, dhcp_magic, 4);
	options += 4;

	// fill message type
	switch (opt_struct -> msg_type) {
	case DHCPDISCOVER :
	case DHCPREQUEST :
	case DHCPDECLINE :
	case DHCPINFORM :
	case DHCPRELEASE :
		options[0] = DHCP_MSG_TYPE;
		options[1] = 1;
		options[2] = opt_struct -> msg_type;
		options += 3;
		break;
	default :
		return 0; // Unsupported DHCP-message
	}

	if (opt_struct -> overload) {
		options[0] = DHCP_OVERLOAD;
		options[1] = 0x01;
		options[2] = opt_struct -> overload;
		options +=3;
	}

	if (opt_struct -> flag[DHCP_REQUESTED_IP]) {
		options[0] = DHCP_REQUESTED_IP;
		options[1] = 0x04;
		* (uint32_t *) (options + 2) = htonl (opt_struct -> requested_IP);
		options +=6;
	}

	if (opt_struct -> flag[DHCP_SERVER_ID]) {
		options[0] = DHCP_SERVER_ID;
		options[1] = 0x04;
		* (uint32_t *) (options + 2) = htonl (opt_struct -> server_ID);
		options +=6;
	}

	sum = 0;
	for (i = 0; i < 256; i++)
		sum += opt_struct -> request_list[i];

	if (sum) {
		options[0] = DHCP_REQUEST_LIST;
		options[1] = sum;
		options += 2;
		for (i = 0; i < 256; i++) {
			if (opt_struct -> request_list[i]) {
				options[0] = i; options++;
			}
		}
	}

	if (opt_struct -> flag[DHCP_TFTP_SERVER]) {
		options[0] = DHCP_TFTP_SERVER;
		options[1] = strlen(opt_struct->tftp_server) + 1;
		memcpy(options + 2, opt_struct -> tftp_server, options[1]);
		options += options[1] + 2;
	}

	if (opt_struct -> flag[DHCP_BOOTFILE]) {
		options[0] = DHCP_BOOTFILE;
		options[1] = strlen(opt_struct->bootfile) + 1;
		memcpy(options + 2, opt_struct -> bootfile, options[1]);
		options += options[1] + 2;
	}

	if (opt_struct -> flag[DHCP_CLIENT_ARCH]) {
		options[0] = DHCP_CLIENT_ARCH;
		options[1] = 2;
		options[2] = (DHCPARCH >> 8);
		options[3] = DHCPARCH & 0xff;
		options += 4;
	}

	// end options
	options[0] = 0xFF;
	options++;

	return 1;
}

/**
 * DHCP: Extracts encoded options from DHCP-message into the structure.
 *       For more information about option coding see dhcp_options_t.
 *
 * @param  opt_field     Points to the "options" field of DHCP-message
 *                       (source).
 * @param  opt_len       Length of "options" field.
 * @param  opt_struct    this structure stores info about the options which
 *                       was extracted from DHCP-message (destination).
 * @return               TRUE - options extracted;
 *                       FALSE - error condition occurs.
 * @see                  dhcp_options_t
 */
static int32_t dhcp_decode_options(uint8_t opt_field[], uint32_t opt_len,
				   dhcp_options_t * opt_struct)
{
	uint32_t offset = 0;

	memset(opt_struct, 0, sizeof(dhcp_options_t));

	// magic
	if (memcmp(opt_field, dhcp_magic, 4)) {
		return 0;
	}

	offset += 4;
	while (offset < opt_len) {
		opt_struct -> flag[opt_field[offset]] = 1;
		switch(opt_field[offset]) {
		case DHCP_OVERLOAD :
			opt_struct -> overload = opt_field[offset + 2];
			offset += 2 + opt_field[offset + 1];
			break;

		case DHCP_REQUESTED_IP :
			opt_struct -> requested_IP = htonl(* (uint32_t *) (opt_field + offset + 2));
			offset += 2 + opt_field[offset + 1];
			break;

		case DHCP_MASK :
			opt_struct -> flag[DHCP_MASK] = 1;
			opt_struct -> subnet_mask = htonl(* (uint32_t *) (opt_field + offset + 2));
			offset += 2 + opt_field[offset + 1];
			break;

		case DHCP_DNS :
			opt_struct -> flag[DHCP_DNS] = 1;
			opt_struct -> dns_IP = htonl(* (uint32_t *) (opt_field + offset + 2));
			offset += 2 + opt_field[offset + 1];
			break;

		case DHCP_ROUTER :
			opt_struct -> flag[DHCP_ROUTER] = 1;
			opt_struct -> router_IP = htonl(* (uint32_t *) (opt_field + offset + 2));
			offset += 2 + opt_field[offset + 1];
			break;

		case DHCP_MSG_TYPE :
			if ((opt_field[offset + 2] > 0) && (opt_field[offset + 2] < 9))
				opt_struct -> msg_type = opt_field[offset + 2];
			else
				return 0;
			offset += 2 + opt_field[offset + 1];
			break;

		case DHCP_SERVER_ID :
			opt_struct -> server_ID = htonl(* (uint32_t *) (opt_field + offset + 2));
			offset += 2 + opt_field[offset + 1];
			break;

		case DHCP_TFTP_SERVER	:
			memcpy(opt_struct -> tftp_server, opt_field + offset + 2, opt_field[offset + 1]);
			(opt_struct -> tftp_server)[opt_field[offset + 1]] = 0;
			offset += 2 + opt_field[offset + 1];
			break;

		case DHCP_BOOTFILE :
			memcpy(opt_struct ->  bootfile, opt_field + offset + 2, opt_field[offset + 1]);
			(opt_struct -> bootfile)[opt_field[offset + 1]] = 0;
			offset += 2 + opt_field[offset + 1];
			break;

		case DHCP_CLIENT_ARCH :
			opt_struct -> client_arch = ((opt_field[offset + 2] << 8) & 0xFF00) | (opt_field[offset + 3] & 0xFF);
			offset += 4;
			break;

		case DHCP_PXELINUX_CFGFILE:
			pxelinux_cfgfile = malloc(opt_field[offset + 1] + 1);
			if (pxelinux_cfgfile) {
				memcpy(pxelinux_cfgfile, opt_field + offset + 2,
				       opt_field[offset + 1]);
				pxelinux_cfgfile[opt_field[offset + 1]] = 0;
			}
			offset += 2 + opt_field[offset + 1];
			break;

		case DHCP_PXELINUX_PREFIX:
			pxelinux_prefix = malloc(opt_field[offset + 1] + 1);
			if (pxelinux_prefix) {
				memcpy(pxelinux_prefix, opt_field + offset + 2,
				       opt_field[offset + 1]);
				pxelinux_prefix[opt_field[offset + 1]] = 0;
			}
			offset += 2 + opt_field[offset + 1];
			break;

		case DHCP_PADOPT :
			offset++;
			break;

		case DHCP_ENDOPT :  // End of options
			return 1;

		default :
			offset += 2 + opt_field[offset + 1]; // Unsupported opt. - do nothing
		}
	}
	if (offset == opt_len)
		return 1; // options finished without 0xFF

	return 0;
}

/**
 * DHCP: Appends information from source "options" into dest "options".
 *       This function is used to support "file/sname" overloading.
 *
 * @param  dst_options   destination "options" field
 * @param  dst_len       size of dst_options (modified by this function)
 * @param  src_options   source "options" field
 * @param  src_len       size of src_options
 * @return               TRUE - options merged;
 *                       FALSE - error condition occurs.
 */
static int8_t dhcp_merge_options(uint8_t dst_options[], uint32_t * dst_len,
				 uint8_t src_options[], uint32_t src_len)
{
	uint32_t dst_offset, src_offset = 0;

	// remove ENDOPT if presented
	if (dhcp_find_option(dst_options, * dst_len, DHCP_ENDOPT, &dst_offset))
		* dst_len = dst_offset;

	while (src_offset < src_len) {
		switch(src_options[src_offset]) {
		case DHCP_PADOPT:
			src_offset++;
			break;
		case DHCP_ENDOPT:
			return 1;
		default:
			if (dhcp_find_option(dst_options, * dst_len,
			                     src_options[src_offset],
			                     &dst_offset)) {
				dhcp_combine_option(dst_options, dst_len,
				                    dst_offset,
				                    (uint8_t *) src_options +
				                    src_offset);
			}
			else {
				dhcp_append_option(dst_options, dst_len, src_options + src_offset);
			}
			src_offset += 2 + src_options[src_offset + 1];
		}
	}

	if (src_offset == src_len)
		return 1;
	return 0;
}

/**
 * DHCP: Finds given occurrence of the option with the given code (op_code)
 *       in "options" field of DHCP-message.
 *
 * @param  options       "options" field of DHCP-message
 * @param  len           length of the "options" field
 * @param  op_code       code of the option to find
 * @param  op_offset     SUCCESS - offset to an option occurrence;
 *                       FAULT - offset is set to zero.
 * @return               TRUE - option was find;
 *                       FALSE - option wasn't find.
 */
static int8_t dhcp_find_option(uint8_t options[], uint32_t len,
			       uint8_t op_code, uint32_t * op_offset)
{
	uint32_t srch_offset = 0;
	* op_offset = 0;

	while (srch_offset < len) {
		if (options[srch_offset] == op_code) {
			* op_offset = srch_offset;
			return 1;
		}
		if (options[srch_offset] == DHCP_ENDOPT)
			return 0;

		if (options[srch_offset] == DHCP_PADOPT)
			srch_offset++;
		else
			srch_offset += 2 + options[srch_offset + 1];
	}
	return 0;
}

/**
 * DHCP: Appends new option from one list (src) into the tail
 *       of another option list (dst)
 *
 * @param  dst_options   "options" field of DHCP-message
 * @param  dst_len       length of the "options" field (modified)
 * @param  new_option    points to an option in another list (src)
 */
static void dhcp_append_option(uint8_t dst_options[], uint32_t * dst_len,
			       uint8_t * new_option)
{
	memcpy(dst_options + ( * dst_len), new_option, 2 + (* (new_option + 1)));
	* dst_len += 2 + *(new_option + 1);
}

/**
 * DHCP: This function is used when options with the same code are
 *       presented in both merged lists. In this case information
 *       about the option from one list (src) is combined (complemented)
 *       with information about the option in another list (dst).
 *
 * @param  dst_options  "options" field of DHCP-message
 * @param  dst_len       length of the "options" field (modified)
 * @param  dst_offset    offset of the option from beginning of the list
 * @param  new_option    points to an option in another list (src)
 */
static void dhcp_combine_option(uint8_t dst_options[], uint32_t * dst_len,
				uint32_t dst_offset, uint8_t * new_option)
{
	uint8_t tmp_buffer[1024]; // use to provide safe memcpy
	uint32_t tail_len;

	// move all subsequent options (allocate size for additional info)
	tail_len = (* dst_len) - dst_offset - 2 - dst_options[dst_offset + 1];

	memcpy(tmp_buffer, dst_options + (* dst_len) - tail_len, tail_len);
	memcpy(dst_options + (* dst_len) - tail_len + (* (new_option + 1)),
	       tmp_buffer, tail_len);

	// add new_content to option
	memcpy(dst_options + (* dst_len) - tail_len, new_option + 2,
	       * (new_option + 1));
	dst_options[dst_offset + 1] += * (new_option + 1);

	// correct dst_len
	* dst_len += * (new_option + 1);
}

/**
 * DHCP: Sends DHCP-Discover message. Looks for DHCP servers.
 */
static void dhcp_send_discover(int fd)
{
	uint32_t packetsize = sizeof(struct iphdr) +
	                      sizeof(struct udphdr) + sizeof(struct btphdr);
	struct btphdr *btph;
	dhcp_options_t opt;

	memset(ether_packet, 0, packetsize);

	btph = (struct btphdr *) (&ether_packet[
	       sizeof(struct iphdr) + sizeof(struct udphdr)]);

	btph -> op = 1;
	btph -> htype = 1;
	btph -> hlen = 6;
	btph -> xid = dhcp_xid;
	memcpy(btph -> chaddr, get_mac_address(), 6);

	memset(&opt, 0, sizeof(dhcp_options_t));

	opt.msg_type = DHCPDISCOVER;

	opt.request_list[DHCP_MASK] = 1;
	opt.request_list[DHCP_DNS] = 1;
	opt.request_list[DHCP_ROUTER] = 1;
	opt.request_list[DHCP_TFTP_SERVER] = 1;
	opt.request_list[DHCP_BOOTFILE] = 1;
	opt.request_list[DHCP_CLIENT_ARCH] = USE_DHCPARCH;

	dhcp_encode_options(btph -> vend, &opt);

	fill_udphdr(&ether_packet[sizeof(struct iphdr)],
	            sizeof(struct btphdr) + sizeof(struct udphdr),
	            UDPPORT_BOOTPC, UDPPORT_BOOTPS);
	fill_iphdr(ether_packet, sizeof(struct btphdr) +
	           sizeof(struct udphdr) + sizeof(struct iphdr),
	           IPTYPE_UDP, dhcp_own_ip, 0xFFFFFFFF);

	send_ipv4(fd, ether_packet, packetsize);
}

/**
 * DHCP: Sends DHCP-Request message. Asks for acknowledgment to occupy IP.
 */
static void dhcp_send_request(int fd)
{
	uint32_t packetsize = sizeof(struct iphdr) +
	                      sizeof(struct udphdr) + sizeof(struct btphdr);
	struct btphdr *btph;
	dhcp_options_t opt;

	memset(ether_packet, 0, packetsize);

	btph = (struct btphdr *) (&ether_packet[
	       sizeof(struct iphdr) + sizeof(struct udphdr)]);

	btph -> op = 1;
	btph -> htype = 1;
	btph -> hlen = 6;
	btph -> xid = dhcp_xid;
	memcpy(btph -> chaddr, get_mac_address(), 6);

	memset(&opt, 0, sizeof(dhcp_options_t));

	opt.msg_type = DHCPREQUEST;
	memcpy(&(opt.requested_IP), &dhcp_own_ip, 4);
	opt.flag[DHCP_REQUESTED_IP] = 1;
	memcpy(&(opt.server_ID), &dhcp_server_ip, 4);
	opt.flag[DHCP_SERVER_ID] = 1;

	opt.request_list[DHCP_MASK] = 1;
	opt.request_list[DHCP_DNS] = 1;
	opt.request_list[DHCP_ROUTER] = 1;
	opt.request_list[DHCP_TFTP_SERVER] = 1;
	opt.request_list[DHCP_BOOTFILE] = 1;
	opt.request_list[DHCP_PXELINUX_CFGFILE] = 1;
	opt.request_list[DHCP_PXELINUX_PREFIX] = 1;

	opt.request_list[DHCP_CLIENT_ARCH] = USE_DHCPARCH;
	opt.flag[DHCP_CLIENT_ARCH] = USE_DHCPARCH;

	dhcp_encode_options(btph -> vend, &opt);

	fill_udphdr(&ether_packet[sizeof(struct iphdr)],
	            sizeof(struct btphdr) + sizeof(struct udphdr),
	            UDPPORT_BOOTPC, UDPPORT_BOOTPS);
	fill_iphdr(ether_packet, sizeof(struct btphdr) +
	           sizeof(struct udphdr) + sizeof(struct iphdr),
	           IPTYPE_UDP, 0, 0xFFFFFFFF);

	send_ipv4(fd, ether_packet, packetsize);
}


/**
 * DHCP: Sends DHCP-Release message. Releases occupied IP.
 */
void dhcp_send_release(int fd)
{
	uint32_t packetsize = sizeof(struct iphdr) +
	                      sizeof(struct udphdr) + sizeof(struct btphdr);
	struct btphdr *btph;
	dhcp_options_t opt;

	btph = (struct btphdr *) (&ether_packet[
	       sizeof(struct iphdr) + sizeof(struct udphdr)]);

	memset(ether_packet, 0, packetsize);

	btph -> op = 1;
	btph -> htype = 1;
	btph -> hlen = 6;
	btph -> xid = dhcp_xid;
	btph -> file[0] = 0;
	memcpy(btph -> chaddr, get_mac_address(), 6);
	btph -> ciaddr = htonl(dhcp_own_ip);

	memset(&opt, 0, sizeof(dhcp_options_t));

	opt.msg_type = DHCPRELEASE;
	opt.server_ID = dhcp_server_ip;
	opt.flag[DHCP_SERVER_ID] = 1;

	dhcp_encode_options(btph -> vend, &opt);

	fill_udphdr(&ether_packet[sizeof(struct iphdr)],
	            sizeof(struct btphdr) + sizeof(struct udphdr),
	            UDPPORT_BOOTPC, UDPPORT_BOOTPS);
	fill_iphdr(ether_packet, sizeof(struct btphdr) +
	           sizeof(struct udphdr) + sizeof(struct iphdr), IPTYPE_UDP,
	           dhcp_own_ip, dhcp_server_ip);

	send_ipv4(fd, ether_packet, packetsize);
}

/**
 * DHCP: Handles DHCP-messages according to Receive-handle diagram.
 *       Changes the state of DHCP-client.
 *
 * @param  fd         socket descriptor
 * @param  packet     BootP/DHCP-packet to be handled
 * @param  packetsize length of the packet
 * @return            ZERO - packet handled successfully;
 *                    NON ZERO - packet was not handled (e.g. bad format)
 * @see               receive_ether
 * @see               btphdr
 */

int8_t handle_dhcp(int fd, uint8_t * packet, int32_t packetsize)
{
	struct btphdr * btph;
	struct iphdr * iph;
	dhcp_options_t opt;

	memset(&opt, 0, sizeof(dhcp_options_t));
	btph = (struct btphdr *) packet;
	iph = (struct iphdr *) packet - sizeof(struct udphdr) -
	      sizeof(struct iphdr);

	if (btph->op != 2)
		return -1;		/* It is not a Bootp/DHCP reply */
	if (btph->xid != dhcp_xid)
		return -1;		/* The transaction ID does not match */

	if (memcmp(btph -> vend, dhcp_magic, 4)) {
		// It is BootP - RFC 951
		dhcp_own_ip    = htonl(btph -> yiaddr);
		dhcp_siaddr_ip = htonl(btph -> siaddr);
		dhcp_server_ip = htonl(iph -> ip_src);

		if (strlen((char *) btph -> sname) && !dhcp_siaddr_ip) {
			strncpy(dhcp_tftp_name, (char *)btph->sname,
			        sizeof(btph->sname));
			dhcp_tftp_name[sizeof(btph -> sname)] = 0;
		}

		if (strlen((char *) btph -> file)) {
			strncpy(dhcp_filename, (char *)btph->file,
			        sizeof(btph->file));
			dhcp_filename[sizeof(btph -> file)] = 0;
		}

		dhcp_state = DHCP_STATE_SUCCESS;
		return 0;
	}


	// decode options
	if (!dhcp_decode_options(btph -> vend, packetsize -
	                         sizeof(struct btphdr) + sizeof(btph -> vend),
	                         &opt)) {
		return -1;  // can't decode options
	}

	if (opt.overload) {
		int16_t decode_res = 0;
		uint8_t options[1024]; // buffer for merged options
		uint32_t opt_len;

		// move 1-st part of options from vend field into buffer
		opt_len = packetsize - sizeof(struct btphdr) +
		          sizeof(btph -> vend) - 4;
		memcpy(options, btph -> vend, opt_len + 4);

		// add other parts
		switch (opt.overload) {
		case DHCP_OVERLOAD_FILE:
			decode_res = dhcp_merge_options(options + 4, &opt_len,
			                                btph -> file,
			                                sizeof(btph -> file));
			break;
		case DHCP_OVERLOAD_SNAME:
			decode_res = dhcp_merge_options(options + 4, &opt_len,
			                                btph -> sname,
			                                sizeof(btph -> sname));
			break;
		case DHCP_OVERLOAD_BOTH:
			decode_res = dhcp_merge_options(options + 4, &opt_len,
			                                btph -> file,
			                                sizeof(btph -> file));
			if (!decode_res)
				break;
			decode_res = dhcp_merge_options(options + 4, &opt_len,
			                                btph -> sname,
			                                sizeof(btph -> sname));
			break;
		}

		if (!decode_res)
			return -1; // bad options in sname/file fields

		// decode merged options
		if (!dhcp_decode_options(options, opt_len + 4, &opt)) {
			return -1; // can't decode options
		}
	}

	if (!opt.msg_type) {
		// It is BootP with Extensions - RFC 1497
		// retrieve conf. settings from BootP - reply
		dhcp_own_ip = htonl(btph -> yiaddr);
		dhcp_siaddr_ip = htonl(btph -> siaddr);
		if (strlen((char *) btph -> sname) && !dhcp_siaddr_ip) {
			strncpy(dhcp_tftp_name, (char *)btph->sname,
			        sizeof(btph->sname));
			dhcp_tftp_name[sizeof(btph -> sname)] = 0;
		}

		if (strlen((char *) btph -> file)) {
			strncpy(dhcp_filename, (char *)btph->file,
			        sizeof(btph->file));
			dhcp_filename[sizeof(btph -> file)] = 0;
		}

		// retrieve DHCP-server IP from IP-header
		dhcp_server_ip = iph -> htonl(ip_src);

		dhcp_state = DHCP_STATE_SUCCESS;
	}
	else {
		// It is DHCP - RFC 2131 & RFC 2132
		// opt contains parameters from server
		switch (dhcp_state) {
		case DHCP_STATE_SELECT :
			if (opt.msg_type == DHCPOFFER) {
				dhcp_own_ip = htonl(btph -> yiaddr);
				dhcp_server_ip = opt.server_ID;
				dhcp_send_request(fd);
				dhcp_state = DHCP_STATE_REQUEST;
			}
			return 0;
		case DHCP_STATE_REQUEST :
			switch (opt.msg_type) {
			case DHCPNACK :
				dhcp_own_ip = 0;
				dhcp_server_ip = 0;
				dhcp_state = DHCP_STATE_FAULT;
				break;
			case DHCPACK :
				dhcp_own_ip = htonl(btph -> yiaddr);
				dhcp_server_ip = opt.server_ID;
				dhcp_siaddr_ip = htonl(btph -> siaddr);
				if (opt.flag[DHCP_TFTP_SERVER]) {
					strcpy(dhcp_tftp_name, opt.tftp_server);
				}
				else {
					dhcp_filename[0] = 0;
					if ((opt.overload != DHCP_OVERLOAD_SNAME &&
					     opt.overload != DHCP_OVERLOAD_BOTH) &&
					     !dhcp_siaddr_ip) {
						strncpy(dhcp_tftp_name,
						        (char *) btph->sname,
						        sizeof(btph -> sname));
						dhcp_tftp_name[sizeof(btph->sname)] = 0;
					}
				}

				if (opt.flag[DHCP_BOOTFILE]) {
					strcpy(dhcp_filename, opt.bootfile);
				}
				else {
					dhcp_filename[0] = 0;
					if (opt.overload != DHCP_OVERLOAD_FILE &&
					    opt.overload != DHCP_OVERLOAD_BOTH &&
					    strlen((char *)btph->file)) {
						strncpy(dhcp_filename,
						        (char *) btph->file,
						        sizeof(btph->file));
						dhcp_filename[sizeof(btph -> file)] = 0;
					}
				}

				dhcp_state = DHCP_STATE_SUCCESS;
				break;
			default:
				break; // Unused DHCP-message - do nothing
			}
			break;
		default :
			return -1; // Illegal DHCP-client state
		}
	}

	if (dhcp_state == DHCP_STATE_SUCCESS) {

		// initialize network entity with real own_ip
		// to be able to answer for foreign requests
		set_ipv4_address(dhcp_own_ip);

		if(response_buffer) {
			if(packetsize <= 1720)
				memcpy(response_buffer, packet, packetsize);
			else
				memcpy(response_buffer, packet, 1720);
		}

		/* Subnet mask */
		if (opt.flag[DHCP_MASK]) {
			/* Router */
			if (opt.flag[DHCP_ROUTER]) {
				set_ipv4_router(opt.router_IP);
				set_ipv4_netmask(opt.subnet_mask);
			}
		}

		/* DNS-server */
		if (opt.flag[DHCP_DNS]) {
			dns_init(opt.dns_IP, 0, 4);
		}
	}

	return 0;
}
