#include "util/packetmaker.h"
#include "util/netsender.h"

extern "C" {
#include <libnet.h>
}

using namespace wibble::sys;

namespace util {

static Buffer buffer_from_libnet(libnet_t* ln_context)
{
	// Construct the packet
	u_char* buf;
	u_int32_t len;
	libnet_adv_cull_packet(ln_context, &buf, &len);
	Buffer pkt(buf, len, false);

	// Waiting for libnet 1.2...
	// FIXME: we HAVE libnet 1.2!
	//libnet_adv_free_packet(ln_context, buf);
	// In the meantime...
	if (ln_context->aligner > 0)
		buf -= ln_context->aligner;
	free(buf);

	libnet_clear_packet(ln_context);

	return pkt;
}

Buffer PacketMaker::makeARPRequest(const IPAddress& ip, const IPAddress& src)
{
	NetSender& sender = NetSender::get();

	unsigned char ether_broadcast_addr[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
	//unsigned char ether_no_addr[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
	struct libnet_ether_addr* localmac = sender.getMACAddress();
	libnet_t* ln_context = sender.getLibnetContext();

	// Build the pieces of the packet
	libnet_build_arp (
			ARPHRD_ETHER,
			ETHERTYPE_IP,
			ETHER_ADDR_LEN,
			4,
			ARPOP_REQUEST,
			localmac->ether_addr_octet,
			(u_char *)src.s_addr_p(),			// FIXME: what's in here?  The broadcast addr maybe?
			ether_broadcast_addr,
			(u_char *)ip.s_addr_p(),
			NULL,
			0,
			ln_context, 0);
	libnet_build_ethernet (
			ether_broadcast_addr,
			localmac->ether_addr_octet,
			ETHERTYPE_ARP,
			NULL, 0,
			ln_context, 0);

	// Construct the packet
	return buffer_from_libnet(ln_context);
}

Buffer PacketMaker::makePingRequest(const ether_addr& mac, const IPAddress& src)
{
	NetSender& sender = NetSender::get();

	//unsigned char ether_broadcast_addr[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
	//unsigned char ether_no_addr[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
	struct libnet_ether_addr* localmac = sender.getMACAddress();
	libnet_t* ln_context = sender.getLibnetContext();
	int id = rand();
	IPAddress noaddr("0.0.0.0");

	// Build the pieces of the packet

	/*
	libnet_build_arp (
			ARPHRD_ETHER,
			ETHERTYPE_IP,
			ETHER_ADDR_LEN,
			4,
			ARPOP_REVREQUEST,
			localmac->ether_addr_octet,
			(u_char *)src.s_addr_p(),			// FIXME: what's in here?  The broadcast addr maybe?
			(u_char *)&mac,
			(u_char *)noaddr.s_addr_p(),
			NULL,
			0,
			ln_context, 0);
	*/

	if (libnet_build_icmpv4_echo(
				ICMP_ECHO,	// type
				0,			// code
				0,			// checksum
				id,			// id
				0,			// seq
				NULL,		// payload
				0,			// payload len
				ln_context,
				0) == -1)
		throw wibble::exception::Libnet(ln_context, "Building ICMPv4 echo packet");

	if (libnet_build_ipv4(
				LIBNET_IPV4_H + LIBNET_ICMPV4_ECHO_H + 0,
				0,			// ToS
				id,			// id
				0,			// frag
				64,			// ttl
				IPPROTO_ICMP,
				0,			// checksum
				src.s_addr(),	// Source IP
				0xffffffff,	// Destination IP
				NULL,		// payload
				0,
				ln_context,
				0) == -1)
		throw wibble::exception::Libnet(ln_context, "Building IPv4 packet");

	if (libnet_build_ethernet(
			(u_char*)&mac,
			localmac->ether_addr_octet,
			ETHERTYPE_IP,
			NULL, 0,
			ln_context, 0) == -1)
		throw wibble::exception::Libnet(ln_context, "Building Ethernet packet");

	// Construct the packet
	return buffer_from_libnet(ln_context);
}

    
Buffer PacketMaker::makeDHCPRequest()
{
	NetSender& sender = NetSender::get();

	unsigned char ether_broadcast_addr[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
	//unsigned char ether_no_addr[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
	struct libnet_ether_addr* localmac = sender.getMACAddress();
	libnet_t* ln_context = sender.getLibnetContext();

	// Packet building taken from dhcp_discover libnet example
	u_char options_req[] = { LIBNET_DHCP_SUBNETMASK , LIBNET_DHCP_BROADCASTADDR , LIBNET_DHCP_TIMEOFFSET , LIBNET_DHCP_ROUTER , LIBNET_DHCP_DOMAINNAME , LIBNET_DHCP_DNS , LIBNET_DHCP_HOSTNAME };

	// Source and destination IP address for discovery
	u_long src_ip = 0;
	u_long dst_ip = 0xffffffff;

	// DHCP Options length
	const int wanted_options_len = 3			// Initial size
				  + sizeof(options_req) + 2		// Options
				  + 1;							// Packet end
	const int options_len = wanted_options_len + LIBNET_DHCPV4_H < LIBNET_BOOTP_MIN_LEN
							? LIBNET_BOOTP_MIN_LEN - LIBNET_DHCPV4_H
							: wanted_options_len;

	// build options packet
	int i = 0;
	u_char* options = new u_char[options_len];
	bzero(options, options_len);
        
	// we are a discover packet
	options[i++] = LIBNET_DHCP_MESSAGETYPE;		// type
	options[i++] = 1;							// len
	options[i++] = LIBNET_DHCP_MSGDISCOVER;		// data
        
	// we are going to request some parameters
	options[i++] = LIBNET_DHCP_PARAMREQUEST;	// type
	options[i++] = sizeof(options_req);			// len
	memcpy(options + i, options_req, sizeof(options_req));	// data
	i += sizeof(options_req);
        
	/*
        // if we have an ip already, let's request it.
        if (src_ip)
        {
            orig_len = options_len;
            options_len += 2 + sizeof(src_ip);
            
            // workaround for realloc on old machines
            options = realloc(options, options_len);
            
            options[i++] = LIBNET_DHCP_DISCOVERADDR;	// type
            options[i++] = sizeof(src_ip);			    // len
            memcpy(options + i, (char *)&src_ip, sizeof(src_ip));// data
            i += sizeof(src_ip);
        }
     */  

	// end our options packet
	options[i++] = LIBNET_DHCP_END;

        
	// Build the DHCP request packet
	libnet_build_dhcpv4(
			LIBNET_DHCP_REQUEST,			// opcode
			1,								// hardware type
			ETHER_ADDR_LEN,					// hardware address length
			0,								// hop count
			0xdeadbeef,						// transaction id
			0,								// seconds since bootstrap
			0x8000,							// flags
			0,								// client ip
			0,								// your ip
			0,								// server ip
			0,								// gateway ip
			localmac->ether_addr_octet,		// client hardware addr
			NULL,							// server host name
			NULL,							// boot file
			options,						// dhcp options stuck in payload since it is dynamic
			options_len,					// length of options
			ln_context,						// libnet handle
			0);								// libnet id

	// wrap in UDP
	libnet_build_udp(
			68,								// source port
			67,								// destination port
			LIBNET_UDP_H + LIBNET_DHCPV4_H + options_len,	// packet size
			0,								// checksum
			NULL,							// payload 
			0,								// payload size 
			ln_context,						// libnet handle 
			0);								// libnet id 
	
	// then in IPv4
	libnet_build_ipv4(
			LIBNET_IPV4_H + LIBNET_UDP_H + LIBNET_DHCPV4_H
			+ options_len,					// length
			0x10,							// TOS
			0,								// IP ID
			0,								// IP Frag 
			16,								// TTL
			IPPROTO_UDP,					// protocol
			0,								// checksum
			src_ip,							// src ip
			dst_ip,							// destination ip
			NULL,							// payload
			0,								// payload size
			ln_context,						// libnet handle
			0);								// libnet id
	
	// we can just autobuild since we arent doing anything tricky
	libnet_autobuild_ethernet(
			ether_broadcast_addr,			// ethernet destination
			ETHERTYPE_IP,					// protocol type
			ln_context);					// libnet handle

	// Construct the packet
	return buffer_from_libnet(ln_context);
}

}
    
// vim:set ts=4 sw=4:
