/*
 * Copyright (c) 2009 Lukas Mejdrech
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * - The name of the author may not be used to endorse or promote products
 *   derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/** @addtogroup ip
 *  @{
 */

/** @file
 *  IP module implementation.
 *  @see arp.h
 */

#include <async.h>
#include <errno.h>
#include <err.h>
#include <fibril_synch.h>
#include <stdio.h>
#include <str.h>
#include <ipc/ipc.h>
#include <ipc/services.h>
#include <ipc/net.h>
#include <ipc/il.h>
#include <sys/types.h>
#include <byteorder.h>

#include <net/socket_codes.h>
#include <net/in.h>
#include <net/in6.h>
#include <net/inet.h>
#include <net/modules.h>
#include <net/device.h>
#include <net/packet.h>
#include <net/icmp_codes.h>

#include <arp_interface.h>
#include <net_checksum.h>
#include <icmp_client.h>
#include <icmp_interface.h>
#include <il_interface.h>
#include <ip_client.h>
#include <ip_interface.h>
#include <net_interface.h>
#include <nil_interface.h>
#include <tl_interface.h>
#include <adt/measured_strings.h>
#include <adt/module_map.h>
#include <packet_client.h>
#include <packet_remote.h>
#include <nil_messages.h>
#include <il_local.h>
#include <ip_local.h>

#include "ip.h"
#include "ip_header.h"
#include "ip_messages.h"
#include "ip_module.h"

/** IP module name.
 */
#define NAME  "ip"

/** IP version 4.
 */
#define IPV4				4

/** Default network interface IP version.
 */
#define NET_DEFAULT_IPV		IPV4

/** Default network interface IP routing.
 */
#define NET_DEFAULT_IP_ROUTING	false

/** Minimum IP packet content.
 */
#define IP_MIN_CONTENT	576

/** ARP module name.
 */
#define ARP_NAME				"arp"

/** ARP module filename.
 */
#define ARP_FILENAME			"/srv/arp"

/** IP packet address length.
 */
#define IP_ADDR							sizeof(struct sockaddr_in6)

/** IP packet prefix length.
 */
#define IP_PREFIX						sizeof(ip_header_t)

/** IP packet suffix length.
 */
#define IP_SUFFIX						0

/** IP packet maximum content length.
 */
#define IP_MAX_CONTENT					65535

/** The IP localhost address.
 */
#define IPV4_LOCALHOST_ADDRESS	htonl((127 << 24) + 1)

/** IP global data.
 */
ip_globals_t	ip_globals;

DEVICE_MAP_IMPLEMENT(ip_netifs, ip_netif_t)

INT_MAP_IMPLEMENT(ip_protos, ip_proto_t)

GENERIC_FIELD_IMPLEMENT(ip_routes, ip_route_t)

/** Updates the device content length according to the new MTU value.
 *  @param[in] device_id The device identifier.
 *  @param[in] mtu The new mtu value.
 *  @returns EOK on success.
 *  @returns ENOENT if device is not found.
 */
int ip_mtu_changed_message(device_id_t device_id, size_t mtu);

/** Updates the device state.
 *  @param[in] device_id The device identifier.
 *  @param[in] state The new state value.
 *  @returns EOK on success.
 *  @returns ENOENT if device is not found.
 */
int ip_device_state_message(device_id_t device_id, device_state_t state);

/** Returns the device packet dimensions for sending.
 *  @param[in] phone The service module phone.
 *  @param[in] message The service specific message.
 *  @param[in] device_id The device identifier.
 *  @param[out] addr_len The minimum reserved address length.
 *  @param[out] prefix The minimum reserved prefix size.
 *  @param[out] content The maximum content size.
 *  @param[out] suffix The minimum reserved suffix size.
 *  @returns EOK on success.
 */
int ip_packet_size_message(device_id_t device_id, size_t * addr_len, size_t * prefix, size_t * content, size_t * suffix);

/** Registers the transport layer protocol.
 *  The traffic of this protocol will be supplied using either the receive function or IPC message.
 *  @param[in] protocol The transport layer module protocol.
 *  @param[in] service The transport layer module service.
 *  @param[in] phone The transport layer module phone.
 *  @param[in] tl_received_msg The receiving function.
 *  @returns EOK on success.
 *  @returns EINVAL if the protocol parameter and/or the service parameter is zero (0).
 *  @returns EINVAL if the phone parameter is not a positive number and the tl_receive_msg is NULL.
 *  @returns ENOMEM if there is not enough memory left.
 */
int ip_register(int protocol, services_t service, int phone, tl_received_msg_t tl_received_msg);

/** Initializes a new network interface specific data.
 *  Connects to the network interface layer module, reads the netif configuration, starts an ARP module if needed and sets the netif routing table.
 *  The device identifier and the nil service has to be set.
 *  @param[in,out] ip_netif Network interface specific data.
 *  @returns EOK on success.
 *  @returns ENOTSUP if DHCP is configured.
 *  @returns ENOTSUP if IPv6 is configured.
 *  @returns EINVAL if any of the addresses is invalid.
 *  @returns EINVAL if the used ARP module is not known.
 *  @returns ENOMEM if there is not enough memory left.
 *  @returns Other error codes as defined for the net_get_device_conf_req() function.
 *  @returns Other error codes as defined for the bind_service() function.
 *  @returns Other error codes as defined for the specific arp_device_req() function.
 *  @returns Other error codes as defined for the nil_packet_size_req() function.
 */
int ip_netif_initialize(ip_netif_ref ip_netif);

/** Sends the packet or the packet queue via the specified route.
 *  The ICMP_HOST_UNREACH error notification may be sent if route hardware destination address is found.
 *  @param[in,out] packet The packet to be sent.
 *  @param[in] netif The target network interface.
 *  @param[in] route The target route.
 *  @param[in] src The source address.
 *  @param[in] dest The destination address.
 *  @param[in] error The error module service.
 *  @returns EOK on success.
 *  @returns Other error codes as defined for the arp_translate_req() function.
 *  @returns Other error codes as defined for the ip_prepare_packet() function.
 */
int ip_send_route(packet_t packet, ip_netif_ref netif, ip_route_ref route, in_addr_t * src, in_addr_t dest, services_t error);

/** Prepares the outgoing packet or the packet queue.
 *  The packet queue is a fragmented packet
 *  Updates the first packet's IP header.
 *  Prefixes the additional packets with fragment headers.
 *  @param[in] source The source address.
 *  @param[in] dest The destination address.
 *  @param[in,out] packet The packet to be sent.
 *  @param[in] destination The destination hardware address.
 *  @returns EOK on success.
 *  @returns EINVAL if the packet is too small to contain the IP header.
 *  @returns EINVAL if the packet is too long than the IP allows.
 *  @returns ENOMEM if there is not enough memory left.
 *  @returns Other error codes as defined for the packet_set_addr() function.
 */
int ip_prepare_packet(in_addr_t * source, in_addr_t dest, packet_t packet, measured_string_ref destination);

/** Checks the packet queue lengths and fragments the packets if needed.
 *  The ICMP_FRAG_NEEDED error notification may be sent if the packet needs to be fragmented and the fragmentation is not allowed.
 *  @param[in,out] packet The packet or the packet queue to be checked.
 *  @param[in] prefix The minimum prefix size.
 *  @param[in] content The maximum content size.
 *  @param[in] suffix The minimum suffix size.
 *  @param[in] addr_len The minimum address length.
 *  @param[in] error The error module service.
 *  @returns The packet or the packet queue of the allowed length.
 *  @returns NULL if there are no packets left.
 */
packet_t ip_split_packet(packet_t packet, size_t prefix, size_t content, size_t suffix, socklen_t addr_len, services_t error);

/** Checks the packet length and fragments it if needed.
 *  The new fragments are queued before the original packet.
 *  @param[in,out] packet The packet to be checked.
 *  @param[in] length The maximum packet length.
 *  @param[in] prefix The minimum prefix size.
 *  @param[in] suffix The minimum suffix size.
 *  @param[in] addr_len The minimum address length.
 *  @returns EOK on success.
 *  @returns EINVAL if the packet_get_addr() function fails.
 *  @returns EINVAL if the packet does not contain the IP header.
 *  @returns EPERM if the packet needs to be fragmented and the fragmentation is not allowed.
 *  @returns ENOMEM if there is not enough memory left.
 *  @returns ENOMEM if there is no packet available.
 *  @returns ENOMEM if the packet is too small to contain the IP header.
 *  @returns Other error codes as defined for the packet_trim() function.
 *  @returns Other error codes as defined for the ip_create_middle_header() function.
 *  @returns Other error codes as defined for the ip_fragment_packet_data() function.
 */
int ip_fragment_packet(packet_t packet, size_t length, size_t prefix, size_t suffix, socklen_t addr_len);

/** Fragments the packet from the end.
 *  @param[in] packet The packet to be fragmented.
 *  @param[in,out] new_packet The new packet fragment.
 *  @param[in,out] header The original packet header.
 *  @param[in,out] new_header The new packet fragment header.
 *  @param[in] length The new fragment length.
 *  @param[in] src The source address.
 *  @param[in] dest The destiantion address.
 *  @param[in] addrlen The address length.
 *  @returns EOK on success.
 *  @returns ENOMEM if the target packet is too small.
 *  @returns Other error codes as defined for the packet_set_addr() function.
 *  @returns Other error codes as defined for the pq_insert_after() function.
 */
int ip_fragment_packet_data(packet_t packet, packet_t new_packet, ip_header_ref header, ip_header_ref new_header, size_t length, const struct sockaddr * src, const struct sockaddr * dest, socklen_t addrlen);

/** Prefixes a middle fragment header based on the last fragment header to the packet.
 *  @param[in] packet The packet to be prefixed.
 *  @param[in] last The last header to be copied.
 *  @returns The prefixed middle header.
 *  @returns NULL on error.
 */
ip_header_ref ip_create_middle_header(packet_t packet, ip_header_ref last);

/** Copies the fragment header.
 *  Copies only the header itself and relevant IP options.
 *  @param[out] last The created header.
 *  @param[in] first The original header to be copied.
 */
void ip_create_last_header(ip_header_ref last, ip_header_ref first);

/** Returns the network interface's IP address.
 *  @param[in] netif The network interface.
 *  @returns The IP address.
 *  @returns NULL if no IP address was found.
 */
in_addr_t * ip_netif_address(ip_netif_ref netif);

/** Searches all network interfaces if there is a suitable route.
 *  @param[in] destination The destination address.
 *  @returns The found route.
 *  @returns NULL if no route was found.
 */
ip_route_ref ip_find_route(in_addr_t destination);

/** Searches the network interfaces if there is a suitable route.
 *  @param[in] netif The network interface to be searched for routes. May be NULL.
 *  @param[in] destination The destination address.
 *  @returns The found route.
 *  @returns NULL if no route was found.
 */
ip_route_ref ip_netif_find_route(ip_netif_ref netif, in_addr_t destination);

/** Processes the received IP packet or the packet queue one by one.
 *  The packet is either passed to another module or released on error.
 *  @param[in] device_id The source device identifier.
 *  @param[in,out] packet The received packet.
 *  @returns EOK on success and the packet is no longer needed.
 *  @returns EINVAL if the packet is too small to carry the IP packet.
 *  @returns EINVAL if the received address lengths differs from the registered values.
 *  @returns ENOENT if the device is not found in the cache.
 *  @returns ENOENT if the protocol for the device is not found in the cache.
 *  @returns ENOMEM if there is not enough memory left.
 */
int ip_receive_message(device_id_t device_id, packet_t packet);

/** Processes the received packet.
 *  The packet is either passed to another module or released on error.
 *  The ICMP_PARAM_POINTER error notification may be sent if the checksum is invalid.
 *  The ICMP_EXC_TTL error notification may be sent if the TTL is less than two (2).
 *  The ICMP_HOST_UNREACH error notification may be sent if no route was found.
 *  The ICMP_HOST_UNREACH error notification may be sent if the packet is for another host and the routing is disabled.
 *  @param[in] device_id The source device identifier.
 *  @param[in] packet The received packet to be processed.
 *  @returns EOK on success.
 *  @returns EINVAL if the TTL is less than two (2).
 *  @returns EINVAL if the checksum is invalid.
 *  @returns EAFNOSUPPORT if the address family is not supported.
 *  @returns ENOENT if no route was found.
 *  @returns ENOENT if the packet is for another host and the routing is disabled.
 */
int ip_process_packet(device_id_t device_id, packet_t packet);

/** Returns the packet destination address from the IP header.
 *  @param[in] header The packet IP header to be read.
 *  @returns The packet destination address.
 */
in_addr_t ip_get_destination(ip_header_ref header);

/** Delivers the packet to the local host.
 *  The packet is either passed to another module or released on error.
 *  The ICMP_PROT_UNREACH error notification may be sent if the protocol is not found.
 *  @param[in] device_id The source device identifier.
 *  @param[in] packet The packet to be delivered.
 *  @param[in] header The first packet IP header. May be NULL.
 *  @param[in] error The packet error service.
 *  @returns EOK on success.
 *  @returns ENOTSUP if the packet is a fragment.
 *  @returns EAFNOSUPPORT if the address family is not supported.
 *  @returns ENOENT if the target protocol is not found.
 *  @returns Other error codes as defined for the packet_set_addr() function.
 *  @returns Other error codes as defined for the packet_trim() function.
 *  @returns Other error codes as defined for the protocol specific tl_received_msg function.
 */
int ip_deliver_local(device_id_t device_id, packet_t packet, ip_header_ref header, services_t error);

/** Prepares the ICMP notification packet.
 *  Releases additional packets and keeps only the first one.
 *  All packets is released on error.
 *  @param[in] error The packet error service.
 *  @param[in] packet The packet or the packet queue to be reported as faulty.
 *  @param[in] header The first packet IP header. May be NULL.
 *  @returns The found ICMP phone.
 *  @returns EINVAL if the error parameter is set.
 *  @returns EINVAL if the ICMP phone is not found.
 *  @returns EINVAL if the ip_prepare_icmp() fails.
 */
int ip_prepare_icmp_and_get_phone(services_t error, packet_t packet, ip_header_ref header);

/** Returns the ICMP phone.
 *  Searches the registered protocols.
 *  @returns The found ICMP phone.
 *  @returns ENOENT if the ICMP is not registered.
 */
int ip_get_icmp_phone(void);

/** Prepares the ICMP notification packet.
 *  Releases additional packets and keeps only the first one.
 *  @param[in] packet The packet or the packet queue to be reported as faulty.
 *  @param[in] header The first packet IP header. May be NULL.
 *  @returns EOK on success.
 *  @returns EINVAL if there are no data in the packet.
 *  @returns EINVAL if the packet is a fragment.
 *  @returns ENOMEM if the packet is too short to contain the IP header.
 *  @returns EAFNOSUPPORT if the address family is not supported.
 *  @returns EPERM if the protocol is not allowed to send ICMP notifications. The ICMP protocol itself.
 *  @returns Other error codes as defined for the packet_set_addr().
 */
int ip_prepare_icmp(packet_t packet, ip_header_ref header);

/** Releases the packet and returns the result.
 *  @param[in] packet The packet queue to be released.
 *  @param[in] result The result to be returned.
 *  @return The result parameter.
 */
int ip_release_and_return(packet_t packet, int result);

int ip_initialize(async_client_conn_t client_connection){
	ERROR_DECLARE;

	fibril_rwlock_initialize(&ip_globals.lock);
	fibril_rwlock_write_lock(&ip_globals.lock);
	fibril_rwlock_initialize(&ip_globals.protos_lock);
	fibril_rwlock_initialize(&ip_globals.netifs_lock);
	ip_globals.packet_counter = 0;
	ip_globals.gateway.address.s_addr = 0;
	ip_globals.gateway.netmask.s_addr = 0;
	ip_globals.gateway.gateway.s_addr = 0;
	ip_globals.gateway.netif = NULL;
	ERROR_PROPAGATE(ip_netifs_initialize(&ip_globals.netifs));
	ERROR_PROPAGATE(ip_protos_initialize(&ip_globals.protos));
	ip_globals.client_connection = client_connection;
	ERROR_PROPAGATE(modules_initialize(&ip_globals.modules));
	ERROR_PROPAGATE(add_module(NULL, &ip_globals.modules, ARP_NAME, ARP_FILENAME, SERVICE_ARP, arp_task_get_id(), arp_connect_module));
	fibril_rwlock_write_unlock(&ip_globals.lock);
	return EOK;
}

int ip_device_req_local(int il_phone, device_id_t device_id, services_t netif){
	ERROR_DECLARE;

	ip_netif_ref ip_netif;
	ip_route_ref route;
	int index;

	ip_netif = (ip_netif_ref) malloc(sizeof(ip_netif_t));
	if(! ip_netif){
		return ENOMEM;
	}
	if(ERROR_OCCURRED(ip_routes_initialize(&ip_netif->routes))){
		free(ip_netif);
		return ERROR_CODE;
	}
	ip_netif->device_id = device_id;
	ip_netif->service = netif;
	ip_netif->state = NETIF_STOPPED;
	fibril_rwlock_write_lock(&ip_globals.netifs_lock);
	if(ERROR_OCCURRED(ip_netif_initialize(ip_netif))){
		fibril_rwlock_write_unlock(&ip_globals.netifs_lock);
		ip_routes_destroy(&ip_netif->routes);
		free(ip_netif);
		return ERROR_CODE;
	}
	if(ip_netif->arp){
		++ ip_netif->arp->usage;
	}
	// print the settings
	printf("%s: Device registered (id: %d, phone: %d, ipv: %d, conf: %s)\n",
	    NAME, ip_netif->device_id, ip_netif->phone, ip_netif->ipv,
	    ip_netif->dhcp ? "dhcp" : "static");
	
	// TODO ipv6 addresses
	
	char address[INET_ADDRSTRLEN];
	char netmask[INET_ADDRSTRLEN];
	char gateway[INET_ADDRSTRLEN];
	
	for (index = 0; index < ip_routes_count(&ip_netif->routes); ++ index){
		route = ip_routes_get_index(&ip_netif->routes, index);
		if (route) {
			inet_ntop(AF_INET, (uint8_t *) &route->address.s_addr, address, INET_ADDRSTRLEN);
			inet_ntop(AF_INET, (uint8_t *) &route->netmask.s_addr, netmask, INET_ADDRSTRLEN);
			inet_ntop(AF_INET, (uint8_t *) &route->gateway.s_addr, gateway, INET_ADDRSTRLEN);
			printf("%s: Route %d (address: %s, netmask: %s, gateway: %s)\n",
			    NAME, index, address, netmask, gateway);
		}
	}
	
	inet_ntop(AF_INET, (uint8_t *) &ip_netif->broadcast.s_addr, address, INET_ADDRSTRLEN);
	printf("%s: Broadcast (%s)\n", NAME, address);
	
	fibril_rwlock_write_unlock(&ip_globals.netifs_lock);
	return EOK;
}

int ip_netif_initialize(ip_netif_ref ip_netif){
	ERROR_DECLARE;

	measured_string_t names[] = {{str_dup("IPV"), 3}, {str_dup("IP_CONFIG"), 9}, {str_dup("IP_ADDR"), 7}, {str_dup("IP_NETMASK"), 10}, {str_dup("IP_GATEWAY"), 10}, {str_dup("IP_BROADCAST"), 12}, {str_dup("ARP"), 3}, {str_dup("IP_ROUTING"), 10}};
	measured_string_ref configuration;
	size_t count = sizeof(names) / sizeof(measured_string_t);
	char * data;
	measured_string_t address;
	int index;
	ip_route_ref route;
	in_addr_t gateway;

	ip_netif->arp = NULL;
	route = NULL;
	ip_netif->ipv = NET_DEFAULT_IPV;
	ip_netif->dhcp = false;
	ip_netif->routing = NET_DEFAULT_IP_ROUTING;
	configuration = &names[0];
	// get configuration
	ERROR_PROPAGATE(net_get_device_conf_req(ip_globals.net_phone, ip_netif->device_id, &configuration, count, &data));
	if(configuration){
		if(configuration[0].value){
			ip_netif->ipv = strtol(configuration[0].value, NULL, 0);
		}
		ip_netif->dhcp = ! str_lcmp(configuration[1].value, "dhcp", configuration[1].length);
		if(ip_netif->dhcp){
			// TODO dhcp
			net_free_settings(configuration, data);
			return ENOTSUP;
		}else if(ip_netif->ipv == IPV4){
			route = (ip_route_ref) malloc(sizeof(ip_route_t));
			if(! route){
				net_free_settings(configuration, data);
				return ENOMEM;
			}
			route->address.s_addr = 0;
			route->netmask.s_addr = 0;
			route->gateway.s_addr = 0;
			route->netif = ip_netif;
			index = ip_routes_add(&ip_netif->routes, route);
			if(index < 0){
				net_free_settings(configuration, data);
				free(route);
				return index;
			}
			if(ERROR_OCCURRED(inet_pton(AF_INET, configuration[2].value, (uint8_t *) &route->address.s_addr))
				|| ERROR_OCCURRED(inet_pton(AF_INET, configuration[3].value, (uint8_t *) &route->netmask.s_addr))
				|| (inet_pton(AF_INET, configuration[4].value, (uint8_t *) &gateway.s_addr) == EINVAL)
				|| (inet_pton(AF_INET, configuration[5].value, (uint8_t *) &ip_netif->broadcast.s_addr) == EINVAL)){
				net_free_settings(configuration, data);
				return EINVAL;
			}
		}else{
			// TODO ipv6 in separate module
			net_free_settings(configuration, data);
			return ENOTSUP;
		}
		if(configuration[6].value){
			ip_netif->arp = get_running_module(&ip_globals.modules, configuration[6].value);
			if(! ip_netif->arp){
				printf("Failed to start the arp %s\n", configuration[6].value);
				net_free_settings(configuration, data);
				return EINVAL;
			}
		}
		if(configuration[7].value){
			ip_netif->routing = (configuration[7].value[0] == 'y');
		}
		net_free_settings(configuration, data);
	}
	// binds the netif service which also initializes the device
	ip_netif->phone = nil_bind_service(ip_netif->service, (ipcarg_t) ip_netif->device_id, SERVICE_IP, ip_globals.client_connection);
	if(ip_netif->phone < 0){
		printf("Failed to contact the nil service %d\n", ip_netif->service);
		return ip_netif->phone;
	}
	// has to be after the device netif module initialization
	if(ip_netif->arp){
		if(route){
			address.value = (char *) &route->address.s_addr;
			address.length = CONVERT_SIZE(in_addr_t, char, 1);
			ERROR_PROPAGATE(arp_device_req(ip_netif->arp->phone, ip_netif->device_id, SERVICE_IP, ip_netif->service, &address));
		}else{
			ip_netif->arp = 0;
		}
	}
	// get packet dimensions
	ERROR_PROPAGATE(nil_packet_size_req(ip_netif->phone, ip_netif->device_id, &ip_netif->packet_dimension));
	if(ip_netif->packet_dimension.content < IP_MIN_CONTENT){
		printf("Maximum transmission unit %d bytes is too small, at least %d bytes are needed\n", ip_netif->packet_dimension.content, IP_MIN_CONTENT);
		ip_netif->packet_dimension.content = IP_MIN_CONTENT;
	}
	index = ip_netifs_add(&ip_globals.netifs, ip_netif->device_id, ip_netif);
	if(index < 0){
		return index;
	}
	if(gateway.s_addr){
		// the default gateway
		ip_globals.gateway.address.s_addr = 0;
		ip_globals.gateway.netmask.s_addr = 0;
		ip_globals.gateway.gateway.s_addr = gateway.s_addr;
		ip_globals.gateway.netif = ip_netif;
	}
	return EOK;
}

int ip_mtu_changed_message(device_id_t device_id, size_t mtu){
	ip_netif_ref netif;

	fibril_rwlock_write_lock(&ip_globals.netifs_lock);
	netif = ip_netifs_find(&ip_globals.netifs, device_id);
	if(! netif){
		fibril_rwlock_write_unlock(&ip_globals.netifs_lock);
		return ENOENT;
	}
	netif->packet_dimension.content = mtu;
	printf("%s: Device %d changed MTU to %d\n", NAME, device_id, mtu);
	fibril_rwlock_write_unlock(&ip_globals.netifs_lock);
	return EOK;
}

int ip_device_state_message(device_id_t device_id, device_state_t state){
	ip_netif_ref netif;

	fibril_rwlock_write_lock(&ip_globals.netifs_lock);
	// find the device
	netif = ip_netifs_find(&ip_globals.netifs, device_id);
	if(! netif){
		fibril_rwlock_write_unlock(&ip_globals.netifs_lock);
		return ENOENT;
	}
	netif->state = state;
	printf("%s: Device %d changed state to %d\n", NAME, device_id, state);
	fibril_rwlock_write_unlock(&ip_globals.netifs_lock);
	return EOK;
}

int ip_register(int protocol, services_t service, int phone, tl_received_msg_t received_msg){
	ip_proto_ref proto;
	int index;

	if(!(protocol && service && ((phone > 0) || (received_msg)))){
		return EINVAL;
	}
	proto = (ip_proto_ref) malloc(sizeof(ip_protos_t));
	if(! proto){
		return ENOMEM;
	}
	proto->protocol = protocol;
	proto->service = service;
	proto->phone = phone;
	proto->received_msg = received_msg;
	fibril_rwlock_write_lock(&ip_globals.protos_lock);
	index = ip_protos_add(&ip_globals.protos, proto->protocol, proto);
	if(index < 0){
		fibril_rwlock_write_unlock(&ip_globals.protos_lock);
		free(proto);
		return index;
	}
	
	printf("%s: Protocol registered (protocol: %d, phone: %d)\n",
	    NAME, proto->protocol, proto->phone);
	
	fibril_rwlock_write_unlock(&ip_globals.protos_lock);
	return EOK;
}

int ip_send_msg_local(int il_phone, device_id_t device_id, packet_t packet, services_t sender, services_t error){
	ERROR_DECLARE;

	int addrlen;
	ip_netif_ref netif;
	ip_route_ref route;
	struct sockaddr * addr;
	struct sockaddr_in * address_in;
//	struct sockaddr_in6 *	address_in6;
	in_addr_t * dest;
	in_addr_t * src;
	int phone;

	// addresses in the host byte order
	// should be the next hop address or the target destination address
	addrlen = packet_get_addr(packet, NULL, (uint8_t **) &addr);
	if(addrlen < 0){
		return ip_release_and_return(packet, addrlen);
	}
	if((size_t) addrlen < sizeof(struct sockaddr)){
		return ip_release_and_return(packet, EINVAL);
	}
	switch(addr->sa_family){
		case AF_INET:
			if(addrlen != sizeof(struct sockaddr_in)){
				return ip_release_and_return(packet, EINVAL);
			}
			address_in = (struct sockaddr_in *) addr;
			dest = &address_in->sin_addr;
			if(! dest->s_addr){
				dest->s_addr = IPV4_LOCALHOST_ADDRESS;
			}
			break;
		// TODO IPv6
/*		case AF_INET6:
			if(addrlen != sizeof(struct sockaddr_in6)){
				return EINVAL;
			}
			address_in6 = (struct sockaddr_in6 *) dest;
			address_in6.sin6_addr.s6_addr;
			IPV6_LOCALHOST_ADDRESS;
*/		default:
			return ip_release_and_return(packet, EAFNOSUPPORT);
	}
	netif = NULL;
	route = NULL;
	fibril_rwlock_read_lock(&ip_globals.netifs_lock);
	// device specified?
	if(device_id > 0){
		netif = ip_netifs_find(&ip_globals.netifs, device_id);
		route = ip_netif_find_route(netif, * dest);
		if(netif && (! route) && (ip_globals.gateway.netif == netif)){
			route = &ip_globals.gateway;
		}
	}
	if(! route){
		route = ip_find_route(*dest);
		netif = route ? route->netif : NULL;
	}
	if(!(netif && route)){
		fibril_rwlock_read_unlock(&ip_globals.netifs_lock);
		phone = ip_prepare_icmp_and_get_phone(error, packet, NULL);
		if(phone >= 0){
			// unreachable ICMP if no routing
			icmp_destination_unreachable_msg(phone, ICMP_NET_UNREACH, 0, packet);
		}
		return ENOENT;
	}
	if(error){
		// do not send for broadcast, anycast packets or network broadcast
		if((! dest->s_addr)
			|| (!(~ dest->s_addr))
			|| (!(~((dest->s_addr &(~ route->netmask.s_addr)) | route->netmask.s_addr)))
			|| (!(dest->s_addr &(~ route->netmask.s_addr)))){
			return ip_release_and_return(packet, EINVAL);
		}
	}
	// if the local host is the destination
	if((route->address.s_addr == dest->s_addr)
		&& (dest->s_addr != IPV4_LOCALHOST_ADDRESS)){
		// find the loopback device to deliver
		dest->s_addr = IPV4_LOCALHOST_ADDRESS;
		route = ip_find_route(*dest);
		netif = route ? route->netif : NULL;
		if(!(netif && route)){
			fibril_rwlock_read_unlock(&ip_globals.netifs_lock);
			phone = ip_prepare_icmp_and_get_phone(error, packet, NULL);
			if(phone >= 0){
				// unreachable ICMP if no routing
				icmp_destination_unreachable_msg(phone, ICMP_HOST_UNREACH, 0, packet);
			}
			return ENOENT;
		}
	}
	src = ip_netif_address(netif);
	if(! src){
		fibril_rwlock_read_unlock(&ip_globals.netifs_lock);
		return ip_release_and_return(packet, ENOENT);
	}
	ERROR_CODE = ip_send_route(packet, netif, route, src, * dest, error);
	fibril_rwlock_read_unlock(&ip_globals.netifs_lock);
	return ERROR_CODE;
}

in_addr_t * ip_netif_address(ip_netif_ref netif){
	ip_route_ref route;

	route = ip_routes_get_index(&netif->routes, 0);
	return route ? &route->address : NULL;
}

int ip_send_route(packet_t packet, ip_netif_ref netif, ip_route_ref route, in_addr_t * src, in_addr_t dest, services_t error){
	ERROR_DECLARE;

	measured_string_t destination;
	measured_string_ref translation;
	char * data;
	int phone;

	// get destination hardware address
	if(netif->arp && (route->address.s_addr != dest.s_addr)){
		destination.value = route->gateway.s_addr ? (char *) &route->gateway.s_addr : (char *) &dest.s_addr;
		destination.length = CONVERT_SIZE(dest.s_addr, char, 1);
		if(ERROR_OCCURRED(arp_translate_req(netif->arp->phone, netif->device_id, SERVICE_IP, &destination, &translation, &data))){
//			sleep(1);
//			ERROR_PROPAGATE(arp_translate_req(netif->arp->phone, netif->device_id, SERVICE_IP, &destination, &translation, &data));
			pq_release_remote(ip_globals.net_phone, packet_get_id(packet));
			return ERROR_CODE;
		}
		if(!(translation && translation->value)){
			if(translation){
				free(translation);
				free(data);
			}
			phone = ip_prepare_icmp_and_get_phone(error, packet, NULL);
			if(phone >= 0){
				// unreachable ICMP if no routing
				icmp_destination_unreachable_msg(phone, ICMP_HOST_UNREACH, 0, packet);
			}
			return EINVAL;
		}
	}else translation = NULL;
	if(ERROR_OCCURRED(ip_prepare_packet(src, dest, packet, translation))){
		pq_release_remote(ip_globals.net_phone, packet_get_id(packet));
	}else{
		packet = ip_split_packet(packet, netif->packet_dimension.prefix, netif->packet_dimension.content, netif->packet_dimension.suffix, netif->packet_dimension.addr_len, error);
		if(packet){
			nil_send_msg(netif->phone, netif->device_id, packet, SERVICE_IP);
		}
	}
	if(translation){
		free(translation);
		free(data);
	}
	return ERROR_CODE;
}

int ip_prepare_packet(in_addr_t * source, in_addr_t dest, packet_t packet, measured_string_ref destination){
	ERROR_DECLARE;

	size_t length;
	ip_header_ref header;
	ip_header_ref last_header;
	ip_header_ref middle_header;
	packet_t next;

	length = packet_get_data_length(packet);
	if((length < sizeof(ip_header_t)) || (length > IP_MAX_CONTENT)){
		return EINVAL;
	}
	header = (ip_header_ref) packet_get_data(packet);
	if(destination){
		ERROR_PROPAGATE(packet_set_addr(packet, NULL, (uint8_t *) destination->value, CONVERT_SIZE(char, uint8_t, destination->length)));
	}else{
		ERROR_PROPAGATE(packet_set_addr(packet, NULL, NULL, 0));
	}
	header->version = IPV4;
	header->fragment_offset_high = 0;
	header->fragment_offset_low = 0;
	header->header_checksum = 0;
	if(source){
		header->source_address = source->s_addr;
	}
	header->destination_address = dest.s_addr;
	fibril_rwlock_write_lock(&ip_globals.lock);
	++ ip_globals.packet_counter;
	header->identification = htons(ip_globals.packet_counter);
	fibril_rwlock_write_unlock(&ip_globals.lock);
//	length = packet_get_data_length(packet);
	if(pq_next(packet)){
		last_header = (ip_header_ref) malloc(IP_HEADER_LENGTH(header));
		if(! last_header){
			return ENOMEM;
		}
		ip_create_last_header(last_header, header);
		next = pq_next(packet);
		while(pq_next(next)){
			middle_header = (ip_header_ref) packet_prefix(next, IP_HEADER_LENGTH(last_header));
			if(! middle_header){
				return ENOMEM;
			}
			memcpy(middle_header, last_header, IP_HEADER_LENGTH(last_header));
			header->flags |= IPFLAG_MORE_FRAGMENTS;
			middle_header->total_length = htons(packet_get_data_length(next));
			middle_header->fragment_offset_high = IP_COMPUTE_FRAGMENT_OFFSET_HIGH(length);
			middle_header->fragment_offset_low = IP_COMPUTE_FRAGMENT_OFFSET_LOW(length);
			middle_header->header_checksum = IP_HEADER_CHECKSUM(middle_header);
			if(destination){
				ERROR_PROPAGATE(packet_set_addr(next, NULL, (uint8_t *) destination->value, CONVERT_SIZE(char, uint8_t, destination->length)));
			}
			length += packet_get_data_length(next);
			next = pq_next(next);
		}
		middle_header = (ip_header_ref) packet_prefix(next, IP_HEADER_LENGTH(last_header));
		if(! middle_header){
			return ENOMEM;
		}
		memcpy(middle_header, last_header, IP_HEADER_LENGTH(last_header));
		middle_header->total_length = htons(packet_get_data_length(next));
		middle_header->fragment_offset_high = IP_COMPUTE_FRAGMENT_OFFSET_HIGH(length);
		middle_header->fragment_offset_low = IP_COMPUTE_FRAGMENT_OFFSET_LOW(length);
		middle_header->header_checksum = IP_HEADER_CHECKSUM(middle_header);
		if(destination){
			ERROR_PROPAGATE(packet_set_addr(next, NULL, (uint8_t *) destination->value, CONVERT_SIZE(char, uint8_t, destination->length)));
		}
		length += packet_get_data_length(next);
		free(last_header);
		header->flags |= IPFLAG_MORE_FRAGMENTS;
	}
	header->total_length = htons(length);
	// unnecessary for all protocols
	header->header_checksum = IP_HEADER_CHECKSUM(header);
	return EOK;
}

int ip_message_standalone(ipc_callid_t callid, ipc_call_t *call,
    ipc_call_t *answer, int * answer_count)
{
	ERROR_DECLARE;
	
	packet_t packet;
	struct sockaddr *addr;
	size_t addrlen;
	size_t prefix;
	size_t suffix;
	size_t content;
	void *header;
	size_t headerlen;
	device_id_t device_id;
	
	*answer_count = 0;
	switch (IPC_GET_METHOD(*call)) {
		case IPC_M_PHONE_HUNGUP:
			return EOK;
		case NET_IL_DEVICE:
			return ip_device_req_local(0, IPC_GET_DEVICE(call),
			    IPC_GET_SERVICE(call));
		case IPC_M_CONNECT_TO_ME:
			return ip_register(IL_GET_PROTO(call), IL_GET_SERVICE(call),
			    IPC_GET_PHONE(call), NULL);
		case NET_IL_SEND:
			ERROR_PROPAGATE(packet_translate_remote(ip_globals.net_phone, &packet,
			    IPC_GET_PACKET(call)));
			return ip_send_msg_local(0, IPC_GET_DEVICE(call), packet, 0,
			    IPC_GET_ERROR(call));
		case NET_IL_DEVICE_STATE:
			return ip_device_state_message(IPC_GET_DEVICE(call),
			    IPC_GET_STATE(call));
		case NET_IL_RECEIVED:
			ERROR_PROPAGATE(packet_translate_remote(ip_globals.net_phone, &packet,
			    IPC_GET_PACKET(call)));
			return ip_receive_message(IPC_GET_DEVICE(call), packet);
		case NET_IP_RECEIVED_ERROR:
			ERROR_PROPAGATE(packet_translate_remote(ip_globals.net_phone, &packet,
			    IPC_GET_PACKET(call)));
			return ip_received_error_msg_local(0, IPC_GET_DEVICE(call), packet,
			    IPC_GET_TARGET(call), IPC_GET_ERROR(call));
		case NET_IP_ADD_ROUTE:
			return ip_add_route_req_local(0, IPC_GET_DEVICE(call),
			    IP_GET_ADDRESS(call), IP_GET_NETMASK(call), IP_GET_GATEWAY(call));
		case NET_IP_SET_GATEWAY:
			return ip_set_gateway_req_local(0, IPC_GET_DEVICE(call),
			    IP_GET_GATEWAY(call));
		case NET_IP_GET_ROUTE:
			ERROR_PROPAGATE(data_receive((void **) &addr, &addrlen));
			ERROR_PROPAGATE(ip_get_route_req_local(0, IP_GET_PROTOCOL(call),
			    addr, (socklen_t) addrlen, &device_id, &header, &headerlen));
			IPC_SET_DEVICE(answer, device_id);
			IP_SET_HEADERLEN(answer, headerlen);
			
			*answer_count = 2;
			
			if (!ERROR_OCCURRED(data_reply(&headerlen, sizeof(headerlen))))
				ERROR_CODE = data_reply(header, headerlen);
			
			free(header);
			return ERROR_CODE;
		case NET_IL_PACKET_SPACE:
			ERROR_PROPAGATE(ip_packet_size_message(IPC_GET_DEVICE(call),
			    &addrlen, &prefix, &content, &suffix));
			IPC_SET_ADDR(answer, addrlen);
			IPC_SET_PREFIX(answer, prefix);
			IPC_SET_CONTENT(answer, content);
			IPC_SET_SUFFIX(answer, suffix);
			*answer_count = 4;
			return EOK;
		case NET_IL_MTU_CHANGED:
			return ip_mtu_changed_message(IPC_GET_DEVICE(call),
			    IPC_GET_MTU(call));
	}
	
	return ENOTSUP;
}

int ip_packet_size_req_local(int ip_phone, device_id_t device_id,
    packet_dimension_ref packet_dimension)
{
	if (!packet_dimension)
		return EBADMEM;
	
	return ip_packet_size_message(device_id, &packet_dimension->addr_len,
	    &packet_dimension->prefix, &packet_dimension->content,
	    &packet_dimension->suffix);
}

int ip_packet_size_message(device_id_t device_id, size_t * addr_len, size_t * prefix, size_t * content, size_t * suffix){
	ip_netif_ref netif;
	int index;

	if(!(addr_len && prefix && content && suffix)){
		return EBADMEM;
	}
	*content = IP_MAX_CONTENT - IP_PREFIX;
	fibril_rwlock_read_lock(&ip_globals.netifs_lock);
	if(device_id < 0){
		*addr_len = IP_ADDR;
		*prefix = 0;
		*suffix = 0;
		for(index = ip_netifs_count(&ip_globals.netifs) - 1; index >= 0; -- index){
			netif = ip_netifs_get_index(&ip_globals.netifs, index);
			if(netif){
				if(netif->packet_dimension.addr_len > * addr_len){
					*addr_len = netif->packet_dimension.addr_len;
				}
				if(netif->packet_dimension.prefix > * prefix){
					*prefix = netif->packet_dimension.prefix;
				}
				if(netif->packet_dimension.suffix > * suffix){
					*suffix = netif->packet_dimension.suffix;
				}
			}
		}
		*prefix = * prefix + IP_PREFIX;
		*suffix = * suffix + IP_SUFFIX;
	}else{
		netif = ip_netifs_find(&ip_globals.netifs, device_id);
		if(! netif){
			fibril_rwlock_read_unlock(&ip_globals.netifs_lock);
			return ENOENT;
		}
		*addr_len = (netif->packet_dimension.addr_len > IP_ADDR) ? netif->packet_dimension.addr_len : IP_ADDR;
		*prefix = netif->packet_dimension.prefix + IP_PREFIX;
		*suffix = netif->packet_dimension.suffix + IP_SUFFIX;
	}
	fibril_rwlock_read_unlock(&ip_globals.netifs_lock);
	return EOK;
}

int ip_add_route_req_local(int ip_phone, device_id_t device_id, in_addr_t address, in_addr_t netmask, in_addr_t gateway){
	ip_route_ref route;
	ip_netif_ref netif;
	int index;

	fibril_rwlock_write_lock(&ip_globals.netifs_lock);
	netif = ip_netifs_find(&ip_globals.netifs, device_id);
	if(! netif){
		fibril_rwlock_write_unlock(&ip_globals.netifs_lock);
		return ENOENT;
	}
	route = (ip_route_ref) malloc(sizeof(ip_route_t));
	if(! route){
		fibril_rwlock_write_unlock(&ip_globals.netifs_lock);
		return ENOMEM;
	}
	route->address.s_addr = address.s_addr;
	route->netmask.s_addr = netmask.s_addr;
	route->gateway.s_addr = gateway.s_addr;
	route->netif = netif;
	index = ip_routes_add(&netif->routes, route);
	if(index < 0){
		free(route);
	}
	fibril_rwlock_write_unlock(&ip_globals.netifs_lock);
	return index;
}

ip_route_ref ip_find_route(in_addr_t destination){
	int index;
	ip_route_ref route;
	ip_netif_ref netif;

	// start with the last netif - the newest one
	index = ip_netifs_count(&ip_globals.netifs) - 1;
	while(index >= 0){
		netif = ip_netifs_get_index(&ip_globals.netifs, index);
		if(netif && (netif->state == NETIF_ACTIVE)){
			route = ip_netif_find_route(netif, destination);
			if(route){
				return route;
			}
		}
		-- index;
	}
	return &ip_globals.gateway;
}

ip_route_ref ip_netif_find_route(ip_netif_ref netif, in_addr_t destination){
	int index;
	ip_route_ref route;

	if(netif){
		// start with the first one - the direct route
		for(index = 0; index < ip_routes_count(&netif->routes); ++ index){
			route = ip_routes_get_index(&netif->routes, index);
			if(route && ((route->address.s_addr &route->netmask.s_addr) == (destination.s_addr &route->netmask.s_addr))){
				return route;
			}
		}
	}
	return NULL;
}

int ip_set_gateway_req_local(int ip_phone, device_id_t device_id, in_addr_t gateway)
{
	ip_netif_ref netif;

	fibril_rwlock_write_lock(&ip_globals.netifs_lock);
	netif = ip_netifs_find(&ip_globals.netifs, device_id);
	if(! netif){
		fibril_rwlock_write_unlock(&ip_globals.netifs_lock);
		return ENOENT;
	}
	ip_globals.gateway.address.s_addr = 0;
	ip_globals.gateway.netmask.s_addr = 0;
	ip_globals.gateway.gateway.s_addr = gateway.s_addr;
	ip_globals.gateway.netif = netif;
	fibril_rwlock_write_unlock(&ip_globals.netifs_lock);
	return EOK;
}

packet_t ip_split_packet(packet_t packet, size_t prefix, size_t content, size_t suffix, socklen_t addr_len, services_t error){
	size_t length;
	packet_t next;
	packet_t new_packet;
	int result;
	int phone;

	next = packet;
	// check all packets
	while(next){
		length = packet_get_data_length(next);
		// too long?
		if(length > content){
			result = ip_fragment_packet(next, content, prefix, suffix, addr_len);
			if(result != EOK){
				new_packet = pq_detach(next);
				if(next == packet){
					// the new first packet of the queue
					packet = new_packet;
				}
				// fragmentation needed?
				if(result == EPERM){
					phone = ip_prepare_icmp_and_get_phone(error, next, NULL);
					if(phone >= 0){
						// fragmentation necessary ICMP
						icmp_destination_unreachable_msg(phone, ICMP_FRAG_NEEDED, content, next);
					}
				}else{
					pq_release_remote(ip_globals.net_phone, packet_get_id(next));
				}
				next = new_packet;
				continue;
			}
		}
		next = pq_next(next);
	}
	return packet;
}

int ip_fragment_packet(packet_t packet, size_t length, size_t prefix, size_t suffix, socklen_t addr_len){
	ERROR_DECLARE;

	packet_t new_packet;
	ip_header_ref header;
	ip_header_ref middle_header;
	ip_header_ref last_header;
	struct sockaddr * src;
	struct sockaddr * dest;
	socklen_t addrlen;
	int result;

	result = packet_get_addr(packet, (uint8_t **) &src, (uint8_t **) &dest);
	if(result <= 0){
		return EINVAL;
	}
	addrlen = (socklen_t) result;
	if(packet_get_data_length(packet) <= sizeof(ip_header_t)){
		return ENOMEM;
	}
	// get header
	header = (ip_header_ref) packet_get_data(packet);
	if(! header){
		return EINVAL;
	}
	// fragmentation forbidden?
	if(header->flags &IPFLAG_DONT_FRAGMENT){
		return EPERM;
	}
	// create the last fragment
	new_packet = packet_get_4_remote(ip_globals.net_phone, prefix, length, suffix, ((addrlen > addr_len) ? addrlen : addr_len));
	if(! new_packet){
		return ENOMEM;
	}
	// allocate as much as originally
	last_header = (ip_header_ref) packet_suffix(new_packet, IP_HEADER_LENGTH(header));
	if(! last_header){
		return ip_release_and_return(packet, ENOMEM);
	}
	ip_create_last_header(last_header, header);
	// trim the unused space
	if(ERROR_OCCURRED(packet_trim(new_packet, 0, IP_HEADER_LENGTH(header) - IP_HEADER_LENGTH(last_header)))){
		return ip_release_and_return(packet, ERROR_CODE);
	}
	// biggest multiple of 8 lower than content
	// TODO even fragmentation?
	length = length &(~ 0x7);// (content / 8) * 8
	if(ERROR_OCCURRED(ip_fragment_packet_data(packet, new_packet, header, last_header, ((IP_HEADER_DATA_LENGTH(header) - ((length - IP_HEADER_LENGTH(header)) &(~ 0x7))) % ((length - IP_HEADER_LENGTH(last_header)) &(~ 0x7))), src, dest, addrlen))){
		return ip_release_and_return(packet, ERROR_CODE);
	}
	// mark the first as fragmented
	header->flags |= IPFLAG_MORE_FRAGMENTS;
	// create middle framgents
	while(IP_TOTAL_LENGTH(header) > length){
		new_packet = packet_get_4_remote(ip_globals.net_phone, prefix, length, suffix, ((addrlen >= addr_len) ? addrlen : addr_len));
		if(! new_packet){
			return ENOMEM;
		}
		middle_header = ip_create_middle_header(new_packet, last_header);
		if(! middle_header){
			return ip_release_and_return(packet, ENOMEM);
		}
		if(ERROR_OCCURRED(ip_fragment_packet_data(packet, new_packet, header, middle_header, (length - IP_HEADER_LENGTH(middle_header)) &(~ 0x7), src, dest, addrlen))){
			return ip_release_and_return(packet, ERROR_CODE);
		}
	}
	// finish the first fragment
	header->header_checksum = IP_HEADER_CHECKSUM(header);
	return EOK;
}

int ip_fragment_packet_data(packet_t packet, packet_t new_packet, ip_header_ref header, ip_header_ref new_header, size_t length, const struct sockaddr * src, const struct sockaddr * dest, socklen_t addrlen){
	ERROR_DECLARE;

	void * data;
	size_t offset;

	data = packet_suffix(new_packet, length);
	if(! data){
		return ENOMEM;
	}
	memcpy(data, ((void *) header) + IP_TOTAL_LENGTH(header) - length, length);
	ERROR_PROPAGATE(packet_trim(packet, 0, length));
	header->total_length = htons(IP_TOTAL_LENGTH(header) - length);
	new_header->total_length = htons(IP_HEADER_LENGTH(new_header) + length);
	offset = IP_FRAGMENT_OFFSET(header) + IP_HEADER_DATA_LENGTH(header);
	new_header->fragment_offset_high = IP_COMPUTE_FRAGMENT_OFFSET_HIGH(offset);
	new_header->fragment_offset_low = IP_COMPUTE_FRAGMENT_OFFSET_LOW(offset);
	new_header->header_checksum = IP_HEADER_CHECKSUM(new_header);
	ERROR_PROPAGATE(packet_set_addr(new_packet, (const uint8_t *) src, (const uint8_t *) dest, addrlen));
	return pq_insert_after(packet, new_packet);
}

ip_header_ref ip_create_middle_header(packet_t packet, ip_header_ref last){
	ip_header_ref middle;

	middle = (ip_header_ref) packet_suffix(packet, IP_HEADER_LENGTH(last));
	if(! middle){
		return NULL;
	}
	memcpy(middle, last, IP_HEADER_LENGTH(last));
	middle->flags |= IPFLAG_MORE_FRAGMENTS;
	return middle;
}

void ip_create_last_header(ip_header_ref last, ip_header_ref first){
	ip_option_ref option;
	size_t next;
	size_t length;

	// copy first itself
	memcpy(last, first, sizeof(ip_header_t));
	length = sizeof(ip_header_t);
	next = sizeof(ip_header_t);
	// process all ip options
	while(next < first->header_length){
		option = (ip_option_ref) (((uint8_t *) first) + next);
		// skip end or noop
		if((option->type == IPOPT_END) || (option->type == IPOPT_NOOP)){
			++ next;
		}else{
			// copy if said so or skip
			if(IPOPT_COPIED(option->type)){
				memcpy(((uint8_t *) last) + length, ((uint8_t *) first) + next, option->length);
				length += option->length;
			}
			// next option
			next += option->length;
		}
	}
	// align 4 byte boundary
	if(length % 4){
		bzero(((uint8_t *) last) + length, 4 - (length % 4));
		last->header_length = length / 4 + 1;
	}else{
		last->header_length = length / 4;
	}
	last->header_checksum = 0;
}

int ip_receive_message(device_id_t device_id, packet_t packet){
	packet_t next;

	do{
		next = pq_detach(packet);
		ip_process_packet(device_id, packet);
		packet = next;
	}while(packet);
	return EOK;
}

int ip_process_packet(device_id_t device_id, packet_t packet){
	ERROR_DECLARE;

	ip_header_ref header;
	in_addr_t dest;
	ip_route_ref route;
	int phone;
	struct sockaddr * addr;
	struct sockaddr_in addr_in;
//	struct sockaddr_in	addr_in6;
	socklen_t addrlen;

	header = (ip_header_ref) packet_get_data(packet);
	if(! header){
		return ip_release_and_return(packet, ENOMEM);
	}
	// checksum
	if((header->header_checksum) && (IP_HEADER_CHECKSUM(header) != IP_CHECKSUM_ZERO)){
		phone = ip_prepare_icmp_and_get_phone(0, packet, header);
		if(phone >= 0){
			// checksum error ICMP
			icmp_parameter_problem_msg(phone, ICMP_PARAM_POINTER, ((size_t) ((void *) &header->header_checksum)) - ((size_t) ((void *) header)), packet);
		}
		return EINVAL;
	}
	if(header->ttl <= 1){
		phone = ip_prepare_icmp_and_get_phone(0, packet, header);
		if(phone >= 0){
			// ttl oxceeded ICMP
			icmp_time_exceeded_msg(phone, ICMP_EXC_TTL, packet);
		}
		return EINVAL;
	}
	// process ipopt and get destination
	dest = ip_get_destination(header);
	// set the addrination address
	switch(header->version){
		case IPVERSION:
			addrlen = sizeof(addr_in);
			bzero(&addr_in, addrlen);
			addr_in.sin_family = AF_INET;
			memcpy(&addr_in.sin_addr.s_addr, &dest, sizeof(dest));
			addr = (struct sockaddr *) &addr_in;
			break;
/*		case IPv6VERSION:
			addrlen = sizeof(dest_in6);
			bzero(&dest_in6, addrlen);
			dest_in6.sin6_family = AF_INET6;
			memcpy(&dest_in6.sin6_addr.s6_addr,);
			dest = (struct sockaddr *) &dest_in;
			break;
*/		default:
			return ip_release_and_return(packet, EAFNOSUPPORT);
	}
	ERROR_PROPAGATE(packet_set_addr(packet, NULL, (uint8_t *) &addr, addrlen));
	route = ip_find_route(dest);
	if(! route){
		phone = ip_prepare_icmp_and_get_phone(0, packet, header);
		if(phone >= 0){
			// unreachable ICMP
			icmp_destination_unreachable_msg(phone, ICMP_HOST_UNREACH, 0, packet);
		}
		return ENOENT;
	}
	if(route->address.s_addr == dest.s_addr){
		// local delivery
		return ip_deliver_local(device_id, packet, header, 0);
	}else{
		// only if routing enabled
		if(route->netif->routing){
			-- header->ttl;
			return ip_send_route(packet, route->netif, route, NULL, dest, 0);
		}else{
			phone = ip_prepare_icmp_and_get_phone(0, packet, header);
			if(phone >= 0){
				// unreachable ICMP if no routing
				icmp_destination_unreachable_msg(phone, ICMP_HOST_UNREACH, 0, packet);
			}
			return ENOENT;
		}
	}
}

/** Notify the IP module about the received error notification packet.
 *
 * @param[in] ip_phone  The IP module phone used for (semi)remote calls.
 * @param[in] device_id The device identifier.
 * @param[in] packet    The received packet or the received packet queue.
 * @param[in] target    The target internetwork module service to be
 *                      delivered to.
 * @param[in] error     The packet error reporting service. Prefixes the
 *                      received packet.
 *
 * @return EOK on success.
 *
 */
int ip_received_error_msg_local(int ip_phone, device_id_t device_id, packet_t packet, services_t target, services_t error){
	uint8_t * data;
	int offset;
	icmp_type_t type;
	icmp_code_t code;
	ip_netif_ref netif;
	measured_string_t address;
	ip_route_ref route;
	ip_header_ref header;

	switch(error){
		case SERVICE_ICMP:
			offset = icmp_client_process_packet(packet, &type, &code, NULL, NULL);
			if(offset < 0){
				return ip_release_and_return(packet, ENOMEM);
			}
			data = packet_get_data(packet);
			header = (ip_header_ref)(data + offset);
			// destination host unreachable?
			if((type == ICMP_DEST_UNREACH) && (code == ICMP_HOST_UNREACH)){
				fibril_rwlock_read_lock(&ip_globals.netifs_lock);
				netif = ip_netifs_find(&ip_globals.netifs, device_id);
				if(netif && netif->arp){
					route = ip_routes_get_index(&netif->routes, 0);
					// from the same network?
					if(route && ((route->address.s_addr &route->netmask.s_addr) == (header->destination_address &route->netmask.s_addr))){
						// clear the ARP mapping if any
						address.value = (char *) &header->destination_address;
						address.length = CONVERT_SIZE(uint8_t, char, sizeof(header->destination_address));
						arp_clear_address_req(netif->arp->phone, netif->device_id, SERVICE_IP, &address);
					}
				}
				fibril_rwlock_read_unlock(&ip_globals.netifs_lock);
			}
			break;
		default:
			return ip_release_and_return(packet, ENOTSUP);
	}
	return ip_deliver_local(device_id, packet, header, error);
}

int ip_deliver_local(device_id_t device_id, packet_t packet, ip_header_ref header, services_t error){
	ERROR_DECLARE;

	ip_proto_ref proto;
	int phone;
	services_t service;
	tl_received_msg_t received_msg;
	struct sockaddr * src;
	struct sockaddr * dest;
	struct sockaddr_in src_in;
	struct sockaddr_in dest_in;
//	struct sockaddr_in	src_in6;
//	struct sockaddr_in	dest_in6;
	socklen_t addrlen;

	if((header->flags &IPFLAG_MORE_FRAGMENTS) || IP_FRAGMENT_OFFSET(header)){
		// TODO fragmented
		return ENOTSUP;
	}else{
		switch(header->version){
			case IPVERSION:
				addrlen = sizeof(src_in);
				bzero(&src_in, addrlen);
				src_in.sin_family = AF_INET;
				memcpy(&dest_in, &src_in, addrlen);
				memcpy(&src_in.sin_addr.s_addr, &header->source_address, sizeof(header->source_address));
				memcpy(&dest_in.sin_addr.s_addr, &header->destination_address, sizeof(header->destination_address));
				src = (struct sockaddr *) &src_in;
				dest = (struct sockaddr *) &dest_in;
				break;
/*			case IPv6VERSION:
				addrlen = sizeof(src_in6);
				bzero(&src_in6, addrlen);
				src_in6.sin6_family = AF_INET6;
				memcpy(&dest_in6, &src_in6, addrlen);
				memcpy(&src_in6.sin6_addr.s6_addr,);
				memcpy(&dest_in6.sin6_addr.s6_addr,);
				src = (struct sockaddr *) &src_in;
				dest = (struct sockaddr *) &dest_in;
				break;
*/			default:
				return ip_release_and_return(packet, EAFNOSUPPORT);
		}
		if(ERROR_OCCURRED(packet_set_addr(packet, (uint8_t *) src, (uint8_t *) dest, addrlen))){
			return ip_release_and_return(packet, ERROR_CODE);
		}
		// trim padding if present
		if((! error) && (IP_TOTAL_LENGTH(header) < packet_get_data_length(packet))){
			if(ERROR_OCCURRED(packet_trim(packet, 0, packet_get_data_length(packet) - IP_TOTAL_LENGTH(header)))){
				return ip_release_and_return(packet, ERROR_CODE);
			}
		}
		fibril_rwlock_read_lock(&ip_globals.protos_lock);
		proto = ip_protos_find(&ip_globals.protos, header->protocol);
		if(! proto){
			fibril_rwlock_read_unlock(&ip_globals.protos_lock);
			phone = ip_prepare_icmp_and_get_phone(error, packet, header);
			if(phone >= 0){
				// unreachable ICMP
				icmp_destination_unreachable_msg(phone, ICMP_PROT_UNREACH, 0, packet);
			}
			return ENOENT;
		}
		if(proto->received_msg){
			service = proto->service;
			received_msg = proto->received_msg;
			fibril_rwlock_read_unlock(&ip_globals.protos_lock);
			ERROR_CODE = received_msg(device_id, packet, service, error);
		}else{
			ERROR_CODE = tl_received_msg(proto->phone, device_id, packet, proto->service, error);
			fibril_rwlock_read_unlock(&ip_globals.protos_lock);
		}
		return ERROR_CODE;
	}
}

in_addr_t ip_get_destination(ip_header_ref header){
	in_addr_t destination;

	// TODO search set ipopt route?
	destination.s_addr = header->destination_address;
	return destination;
}

int ip_prepare_icmp(packet_t packet, ip_header_ref header){
	packet_t next;
	struct sockaddr * dest;
	struct sockaddr_in dest_in;
//	struct sockaddr_in	dest_in6;
	socklen_t addrlen;

	// detach the first packet and release the others
	next = pq_detach(packet);
	if(next){
		pq_release_remote(ip_globals.net_phone, packet_get_id(next));
	}
	if(! header){
		if(packet_get_data_length(packet) <= sizeof(ip_header_t)){
			return ENOMEM;
		}
		// get header
		header = (ip_header_ref) packet_get_data(packet);
		if(! header){
			return EINVAL;
		}
	}
	// only for the first fragment
	if(IP_FRAGMENT_OFFSET(header)){
		return EINVAL;
	}
	// not for the ICMP protocol
	if(header->protocol == IPPROTO_ICMP){
		return EPERM;
	}
	// set the destination address
	switch(header->version){
		case IPVERSION:
			addrlen = sizeof(dest_in);
			bzero(&dest_in, addrlen);
			dest_in.sin_family = AF_INET;
			memcpy(&dest_in.sin_addr.s_addr, &header->source_address, sizeof(header->source_address));
			dest = (struct sockaddr *) &dest_in;
			break;
/*		case IPv6VERSION:
			addrlen = sizeof(dest_in6);
			bzero(&dest_in6, addrlen);
			dest_in6.sin6_family = AF_INET6;
			memcpy(&dest_in6.sin6_addr.s6_addr,);
			dest = (struct sockaddr *) &dest_in;
			break;
*/		default:
			return EAFNOSUPPORT;
	}
	return packet_set_addr(packet, NULL, (uint8_t *) dest, addrlen);
}

int ip_get_icmp_phone(void){
	ip_proto_ref proto;
	int phone;

	fibril_rwlock_read_lock(&ip_globals.protos_lock);
	proto = ip_protos_find(&ip_globals.protos, IPPROTO_ICMP);
	phone = proto ? proto->phone : ENOENT;
	fibril_rwlock_read_unlock(&ip_globals.protos_lock);
	return phone;
}

int ip_prepare_icmp_and_get_phone(services_t error, packet_t packet, ip_header_ref header){
	int phone;

	phone = ip_get_icmp_phone();
	if(error || (phone < 0) || ip_prepare_icmp(packet, header)){
		return ip_release_and_return(packet, EINVAL);
	}
	return phone;
}

int ip_release_and_return(packet_t packet, int result){
	pq_release_remote(ip_globals.net_phone, packet_get_id(packet));
	return result;
}

int ip_get_route_req_local(int ip_phone, ip_protocol_t protocol, const struct sockaddr * destination, socklen_t addrlen, device_id_t * device_id, void **header, size_t * headerlen){
	struct sockaddr_in * address_in;
//	struct sockaddr_in6 *	address_in6;
	in_addr_t * dest;
	in_addr_t * src;
	ip_route_ref route;
	ipv4_pseudo_header_ref header_in;

	if(!(destination && (addrlen > 0))){
		return EINVAL;
	}
	if(!(device_id && header && headerlen)){
		return EBADMEM;
	}
	if((size_t) addrlen < sizeof(struct sockaddr)){
		return EINVAL;
	}
	switch(destination->sa_family){
		case AF_INET:
			if(addrlen != sizeof(struct sockaddr_in)){
				return EINVAL;
			}
			address_in = (struct sockaddr_in *) destination;
			dest = &address_in->sin_addr;
			if(! dest->s_addr){
				dest->s_addr = IPV4_LOCALHOST_ADDRESS;
			}
			break;
		// TODO IPv6
/*		case AF_INET6:
			if(addrlen != sizeof(struct sockaddr_in6)){
				return EINVAL;
			}
			address_in6 = (struct sockaddr_in6 *) dest;
			address_in6.sin6_addr.s6_addr;
*/		default:
			return EAFNOSUPPORT;
	}
	fibril_rwlock_read_lock(&ip_globals.lock);
	route = ip_find_route(*dest);
	// if the local host is the destination
	if(route && (route->address.s_addr == dest->s_addr)
		&& (dest->s_addr != IPV4_LOCALHOST_ADDRESS)){
		// find the loopback device to deliver
		dest->s_addr = IPV4_LOCALHOST_ADDRESS;
		route = ip_find_route(*dest);
	}
	if(!(route && route->netif)){
		fibril_rwlock_read_unlock(&ip_globals.lock);
		return ENOENT;
	}
	*device_id = route->netif->device_id;
	src = ip_netif_address(route->netif);
	fibril_rwlock_read_unlock(&ip_globals.lock);
	*headerlen = sizeof(*header_in);
	header_in = (ipv4_pseudo_header_ref) malloc(*headerlen);
	if(! header_in){
		return ENOMEM;
	}
	bzero(header_in, * headerlen);
	header_in->destination_address = dest->s_addr;
	header_in->source_address = src->s_addr;
	header_in->protocol = protocol;
	header_in->data_length = 0;
	*header = header_in;
	return EOK;
}

/** Default thread for new connections.
 *
 *  @param[in] iid The initial message identifier.
 *  @param[in] icall The initial message call structure.
 *
 */
static void il_client_connection(ipc_callid_t iid, ipc_call_t * icall)
{
	/*
	 * Accept the connection
	 *  - Answer the first IPC_M_CONNECT_ME_TO call.
	 */
	ipc_answer_0(iid, EOK);
	
	while(true) {
		ipc_call_t answer;
		int answer_count;
		
		/* Clear the answer structure */
		refresh_answer(&answer, &answer_count);
		
		/* Fetch the next message */
		ipc_call_t call;
		ipc_callid_t callid = async_get_call(&call);
		
		/* Process the message */
		int res = il_module_message_standalone(callid, &call, &answer,
		    &answer_count);
		
		/* End if said to either by the message or the processing result */
		if ((IPC_GET_METHOD(call) == IPC_M_PHONE_HUNGUP) || (res == EHANGUP))
			return;
		
		/* Answer the message */
		answer_call(callid, res, &answer, answer_count);
	}
}

/** Starts the module.
 *
 *  @param argc The count of the command line arguments. Ignored parameter.
 *  @param argv The command line parameters. Ignored parameter.
 *
 *  @returns EOK on success.
 *  @returns Other error codes as defined for each specific module start function.
 *
 */
int main(int argc, char *argv[])
{
	ERROR_DECLARE;
	
	/* Start the module */
	if (ERROR_OCCURRED(il_module_start_standalone(il_client_connection)))
		return ERROR_CODE;
	
	return EOK;
}

/** @}
 */
