/*
 * Acess2 IP Stack
 * - IPv6 Protcol Handling
 */
#include "ipstack.h"
#include "link.h"
#include "ipv6.h"
#include "firewall.h"

// === IMPORTS ===
extern tInterface	*gIP_Interfaces;
extern Uint32	IPv4_Netmask(int FixedBits);

// === PROTOTYPES ===
 int	IPv6_Initialise();
 int	IPv6_RegisterCallback(int ID, tIPCallback Callback);
void	IPv6_int_GetPacket(tAdapter *Interface, tMacAddr From, int Length, void *Buffer);
tInterface	*IPv6_GetInterface(tAdapter *Adapter, tIPv6 Address, int Broadcast);

// === GLOBALS ===
tIPCallback	gaIPv6_Callbacks[256];

// === CODE ===
/**
 * \brief Initialise the IPv6 handling code
 */
int IPv6_Initialise()
{
	Link_RegisterType(IPV6_ETHERNET_ID, IPv6_int_GetPacket);
	return 1;
}

/**
 * \brief Registers a callback
 * \param ID	8-bit packet type ID
 * \param Callback	Callback function
 */
int IPv6_RegisterCallback(int ID, tIPCallback Callback)
{
	if( ID < 0 || ID > 255 )	return 0;
	if( gaIPv6_Callbacks[ID] )	return 0;
	gaIPv6_Callbacks[ID] = Callback;
	return 1;
}

/**
 * \brief Creates and sends an IPv6 Packet
 * \param Iface	Interface
 * \param Destination	Destination IP
 * \param Protocol	Protocol ID
 * \param Length	Data Length
 * \param Data	Packet Data
 * \return Boolean Success
 */
int IPv6_SendPacket(tInterface *Iface, tIPv6 Destination, int Protocol, size_t Length, const void *Data)
{
	return 0;
}

/**
 * \fn void IPv6_int_GetPacket(tInterface *Interface, tMacAddr From, int Length, void *Buffer)
 * \brief Process an IPv6 Packet
 * \param Interface	Input interface
 * \param From	Source MAC address
 * \param Length	Packet length
 * \param Buffer	Packet data
 */
void IPv6_int_GetPacket(tAdapter *Adapter, tMacAddr From, int Length, void *Buffer)
{
	tInterface	*iface;
	tIPv6Header	*hdr = Buffer;
	 int	ret;
	char	*dataPtr;
	Uint8	nextHeader;
	
	if(Length < sizeof(tIPv6Header))	return;
	
	hdr->Head = ntohl(hdr->Head);
	
	//if( ((hdr->Head >> (20+8)) & 0xF) != 6 )
	if( hdr->Version != 6 )
		return;
	
	#if 1
	Log_Debug("IPv6", "hdr = {");
	Log_Debug("IPv6", " .Version       = %i", hdr->Version );
	Log_Debug("IPv6", " .TrafficClass  = %i", hdr->TrafficClass );
	Log_Debug("IPv6", " .FlowLabel     = %i", hdr->FlowLabel );
	Log_Debug("IPv6", " .PayloadLength = 0x%04x", ntohs(hdr->PayloadLength) );
	Log_Debug("IPv6", " .NextHeader    = 0x%02x", hdr->NextHeader );
	Log_Debug("IPv6", " .HopLimit      = 0x%02x", hdr->HopLimit );
	Log_Debug("IPv6", " .Source        = %04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x", hdr->Source );
	Log_Debug("IPv6", " .Destination   = %04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x", hdr->Destination );
	Log_Debug("IPv6", "}");
	#endif
	
	// No checksum in IPv6
	
	// Check Packet length
	if( ntohs(hdr->PayloadLength)+sizeof(tIPv6Header) > Length) {
		Log_Log("IPv6", "hdr->PayloadLength(%i) > Length(%i)", ntohs(hdr->PayloadLength), Length);
		return;
	}
	
	// Process Options
	nextHeader = hdr->NextHeader;
	dataPtr = hdr->Data;
	for( ;; )
	{
		struct {
			Uint8	NextHeader;
			Uint8	Length;	// In 8-byte chunks, with 0 being 8 bytes long
			Uint8	Data[];
		}	*optionHdr;
		optionHdr = (void*)dataPtr;
		// Hop-by-hop options
		if(nextHeader == 0)
		{
			// TODO: Parse the options (actually, RFC2460 doesn't specify any)
		}
		// Routing Options
		else if(nextHeader == 43)
		{
			// TODO: Routing Header options
		}
		else
		{
			break;	// Unknown, pass on
		}
		nextHeader = optionHdr->NextHeader;
		dataPtr += (optionHdr->Length + 1) * 8;	// 8-octet length (0 = 8 bytes long)
	}
	
	// Get Interface (allowing broadcasts)
	iface = IPv6_GetInterface(Adapter, hdr->Destination, 1);
	
	// Firewall rules
	if( iface ) {
		// Incoming Packets
		ret = IPTables_TestChain("INPUT",
			6, &hdr->Source, &hdr->Destination,
			hdr->NextHeader, 0,
			hdr->PayloadLength, hdr->Data
			);
	}
	else {
		// Routed packets
		ret = IPTables_TestChain("FORWARD",
			6, &hdr->Source, &hdr->Destination,
			hdr->NextHeader, 0,
			hdr->PayloadLength, hdr->Data
			);
	}
	
	switch(ret)
	{
	// 0 - Allow
	case 0:	break;
	// 1 - Silent Drop
	case 1:
		Log_Debug("IPv6", "Silently dropping packet");
		return ;
	// Unknown, silent drop
	default:
		return ;
	}
	
	// Routing
	if(!iface)
	{
		#if 0
		tMacAddr	to;
		tRoute	*rt;
		
		Log_Debug("IPv6", "Route the packet");
		// Drop the packet if the TTL is zero
		if( hdr->HopLimit == 0 ) {
			Log_Warning("IPv6", "TODO: Sent ICMP-Timeout when TTL exceeded");
			return ;
		}
		
		hdr->HopLimit --;
		
		rt = IPStack_FindRoute(6, NULL, &hdr->Destination);	// Get the route (gets the interface)
		to = ICMP6_ResolveHWAddr(rt->Interface, hdr->Destination);	// Resolve address
		
		// Send packet
		Log_Log("IPv6", "Forwarding packet");
		Link_SendPacket(rt->Interface->Adapter, IPV6_ETHERNET_ID, to, Length, Buffer);
		#endif
		
		return ;
	}
	
	// Send it on
	if( !gaIPv6_Callbacks[hdr->NextHeader] ) {
		Log_Log("IPv6", "Unknown Protocol %i", hdr->NextHeader);
		return ;
	}
	
	gaIPv6_Callbacks[hdr->NextHeader]( iface, &hdr->Source, hdr->PayloadLength, hdr->Data );
}

/**
 * \fn tInterface *IPv6_GetInterface(tAdapter *Adapter, tIPv6 Address)
 * \brief Searches an adapter for a matching address
 * \param Adapter	Source adapter
 * \param Address	Destination Address
 * \param Broadcast	Allow broadcast?
 */
tInterface *IPv6_GetInterface(tAdapter *Adapter, tIPv6 Address, int Broadcast)
{
	 int	i, j;
	tInterface	*iface = NULL;
	Uint32	netmask;
	
	for( iface = gIP_Interfaces; iface; iface = iface->Next)
	{
		tIPv6	*thisAddr;
		
		// Check for this adapter
		if( iface->Adapter != Adapter )	continue;
		
		// Skip non-IPv6 Interfaces
		if( iface->Type != 6 )	continue;
		
		thisAddr = (tIPv6*)iface->Address;
		// If the address is a perfect match, return this interface
		if( IP6_EQU(Address, *thisAddr) )	return iface;
		
		// Check if we want to match broadcast addresses
		if( !Broadcast )	continue;
		
		// Check for broadcast
		// - Check first DWORDs
		if( iface->SubnetBits > 32 && Address.L[0] != thisAddr->L[0] )
			continue;
		if( iface->SubnetBits > 64 && Address.L[1] != thisAddr->L[1] )
			continue;
		if( iface->SubnetBits > 96 && Address.L[2] != thisAddr->L[2] )
			continue;
		
		// Check final DWORD
		j = iface->SubnetBits / 32;
		i = iface->SubnetBits % 32;
		netmask = IPv4_Netmask( iface->SubnetBits % 32 );
		
		// Check the last bit of the netmask
		if( (Address.L[j] >> i) != (thisAddr->L[j] >> i) )	continue;
		
		// Check that the host portion is one
		if( (Address.L[j] & ~netmask) != (0xFFFFFFFF & ~netmask) )	continue;
		if( j >= 2 && Address.L[3] != 0xFFFFFFFF)	continue;
		if( j >= 1 && Address.L[2] != 0xFFFFFFFF)	continue;
		if( j >= 0 && Address.L[1] != 0xFFFFFFFF)	continue;
		
		return iface;
	}
	return NULL;
}
