// Copyright (c) 2006-2009 Nokia Corporation and/or its subsidiary(-ies).
// All rights reserved.
// This component and the accompanying materials are made available
// under the terms of "Eclipse Public License v1.0"
// which accompanies this distribution, and is available
// at the URL "http://www.eclipse.org/legal/epl-v10.html".
//
// Initial Contributors:
// Nokia Corporation - initial contribution.
//
// Contributors:
//
// Description:
// in_pkt.cpp - packet handling routines
// Mainly the RMBufPacketPeek implementation
// + extension header locations
// + method for adding a destination option
//

#include "in_pkt.h"
#include "in_pkt_platform.h"
#include "ext_hdr.h"
#include <nifmbuf.h>

#define OPTIMIZE_PADDING


// RMBufPacketPeek
// ***************

EXPORT_C TUint8 *TInet6PacketBase::Access(RMBufChain &aPacket, TInt aOffset, TInt aSize, TInt aMin)
	/**
	* Accesses a contiguous block of memory within RMBufChain starting
	* at specified offset.
	*
	* Attempts to map aSize amount of contiguous RMBuf space at
	* the specified offset, by rearranging the RMBufs,
	* if necessary.
	*
	* If aSize amount is not available, the returned pointer maps
	* the maximum available space (< aSize).
	*
	* @param	aSize
	*	The size of the requested contiguous mapping (in octets).
	*	The value cannot be larger than KMBufSmallSize. If aSize <= 1,
	*	the method will return availabe contiguous mapping at the
	*	indicated offset without rearranging the RMBuf chain. Values
	*	larger than 1 may require rearrangement to get the requested
	*	area into single RMBuf.
	* @param	aOffset
	*	The offset to the beginning of the area. The value MUST
	*	be in range: 0 <= aOffset <= Length().
	* @param	aMin
	*	The minimum accepted iLength. If iLength is less that aMin,
	*	then returns NULL.
	* @return
	*	A pointer to the data starting at indicated
	*	offset. The iLength is set to the
	*	maximum available contiguous data starting from the
	*	requested offset. iLength is always <= KMBufSmallSize.
	* @note
	*	The length can be either less or
	*	larger than aSize. The caller must verify the returned
	*	length to detect whether Access succeeded.
	*/
	{

	//
	// Can only map at most KMBufSmallSize bytes
	// (truncate aSize to avoid Panics from the Align)
	//
	if (aSize > KMBufSmallSize)
		aSize = KMBufSmallSize;

	for (int already_tried = 0; ;already_tried = 1)
		{
		RMBuf *p;
		TInt offset, len;
		iLength = 0;

		if (aPacket.IsEmpty() || !aPacket.Goto(aOffset, p, offset, len))
			return NULL;
		else if ((aSize <= len && (offset & iAlign) == 0) || already_tried)
			{
			if (len < aMin)
				return NULL;
			iLength = len;
			return p->Buffer() + offset;
			}
		// The requested alignment value is not
		// satisfied!
		//
		// Brute force buffer mangling here: just
		// Split the chain at requested point, apply
		// align to the tail part, and then join the
		// pieces together (assuming this join operation
		// does not undo the align...)
		// (If this is going to occur frequently
		// some optimizations is in order... -- msa)
		//
		//
		// *NOTE*
		//		If aOffset == 0, SplitL works weird (IMHO): it does
		//		nothing (when it logically should move all to the
		//		tail part!). Thus, zero offset needs to be handled
		//		specially (sort of optimizing, so it's not too bad)
		//		-- msa
		//
		if (aOffset > 0)
			{
			RMBufChain tail;
			TInt err = aPacket.Split(aOffset, tail);
			if (err == KErrNone && !tail.IsEmpty())
				{
				tail.Align(aSize);
				aPacket.Append(tail);
				}
			}
		else
			aPacket.Align(aSize);
		// And now just retry the Goto and use that
		// result as is (either we succeeded or not)
		}
	}

EXPORT_C TPtr8 RMBufPacketPeek::Access(TInt aSize, TUint aOffset)
	/**
	* Accesses a contiguous block of memory within RMBufChain starting
	* at specified offset.
	*
	* Attempts to map aSize amount of contiguous RMBuf space at
	* the specified offset into a TPtr8, by rearranging the RMBufs,
	* if necessary.
	*
	* If aSize amount is not available, the returned pointer maps
	* the maximum available space (< aSize).
	*
	* @param	aSize
	*	The size of the requested contiguous mapping (in octets).
	*	The value cannot be larger than KMBufSmallSize. If aSize <= 1,
	*	the method will return availabe contiguous mapping at the
	*	indicated offset without rearranging the RMBuf chain. Values
	*	larger than 1 may require rearrangement to get the requested
	*	area into single RMBuf.
	* @param	aOffset
	*	The offset to the beginning of the area. The value MUST
	*	be in range: 0 <= aOffset <= Length().
	* @return
	*	A pointer descriptor for the data starting at indicated
	*	offset. The length of the descriptor is set to the
	*	maximum available contiguous data starting from the
	*	requested offset. Length is always <= KMBufSmallSize.
	* @note
	*	The length can be either less or
	*	larger than aSize. The caller must verify the returned
	*	length to detect whether Access succeeded.
	*/
	{
	TInet6PacketBase map(TInet6PacketBase::EAlign1);
	TUint8 *const ptr = map.Access(*this, aOffset, aSize, 1);
	return TPtr8(ptr, map.iLength, map.iLength);
	}


EXPORT_C TIpHeader *RMBufPacketPeek::GetIpHeader()
	/**
	* Access IP header (v4 or v6) from the packet at offset 0.
	*
	* @return IP header (or NULL).
	*	A non-NULL return implies that full header, either IPv4 or
	*	IPv6 is accessible through the pointer. This method is
	*	intended to save code space, and not necessarily for
	*	speed...
	*/
	{
	TInet6Packet<TIpHeader> ip(*this);
	if (ip.iHdr)
		{
		if (ip.iHdr->ip4.Version() == 4)
			{
			// Need to do sanity check on header length (because it can be garbage)
			const TInt hlen = ip.iHdr->ip4.HeaderLength();
			if (hlen >= TInet6HeaderIP4::MinHeaderLength() && ip.iLength >= hlen)
				return (TIpHeader *)ip.iHdr;
			}
		else if (ip.iHdr->ip4.Version() == 6)
			{
			if (ip.iLength >= TInet6HeaderIP::MinHeaderLength())
				return (TIpHeader *)ip.iHdr;
			}
		}
	return NULL;
	}

// TPacketPocker
// *************

EXPORT_C TPacketPoker::TPacketPoker(RMBufChain &aChain) : iCurrent(aChain.First()), iOffset(0), iTail(0)
	/**
	* Constructor.
	*
	* @param aChain	The RMBuf chain to be poked.
	*/
	{
	// Find initial value for iTail.
	while (iCurrent)
		{
		iTail = iCurrent->Length();
		if (iTail > 0)
			break;
		// Ugh. There have been RMBuf's with ZERO length!
		iCurrent = iCurrent->Next();
		}
	}

EXPORT_C void TPacketPoker::OverL(TInt aSize)
	/**
	* Skip over bytes.
	*
	* @param aSize	The number of bytes to skip
	* @leave KErrEof, if skipped past end of chain.
	*/
	{
	for (;;)
		{
		if (aSize < iTail)
			{
			iTail -= aSize;		// Results always: iTail > 0!
			iOffset += aSize;
			return;
			}
		aSize -= iTail;
		if ((iCurrent = iCurrent->Next()) == NULL)
			{
			iTail = 0;			// All scanned, at end of chain
			if (aSize > 0)		// --> Error, if request was more
				User::Leave(KErrEof);	// KErrEof used (for lack of any better)
			return;
			}
		iOffset = 0;
		iTail = iCurrent->Length();
		}
	}

EXPORT_C TUint8 *TPacketPoker::AdjustL(TInt aSize)
	/**
	* Arrange contiguous run of bytes.
	*
	* Arragen content of RMBuf chain so that starting from
	* the current offset, a number of bytes is accessible
	* in contiguous memory.
	*
	* @param aSize The requested length,
	* @return The pointer to be beginning of area.
	* @leave KErrEof, if request cannot be satisfied.
	*/
	{
	RMBufChain chain(iCurrent);
	if (iOffset > 0)
		{
		RMBufChain tail;
		chain.SplitL(iOffset, tail);
		tail.Align(aSize);
		chain.Append(tail);
		}
	else
		chain.Align(aSize);
	ASSERT(iCurrent == chain.First());
	iTail = iCurrent->Length() - iOffset;
	if (iTail < aSize)			// Align failed?
		User::Leave(KErrEof);	// KErrEof used (for lack of any better)
	return Ptr();
	}

//

EXPORT_C TBool TPacketPoker::IsExtensionHeader(TInt aProtocolId)
	/**
	* Tests whether a protocol is a known IPv6 extension header using the standard format.
	* 
	* @param aProtocolId	Protocol ID to test.
	* @return ETrue,
	* 	if the protocol header follows the generic IPv6 extension
	*	header format (TInet6HeaderExtension).
	*/
	{
	// If more of these are known, could perhaps implement a
	// static boolean table indexed by protocol -- msa
	return
		(
		aProtocolId == STATIC_CAST(TInt, KProtocolInet6HopOptions) ||
		aProtocolId == STATIC_CAST(TInt, KProtocolInet6DestinationOptions) ||
		aProtocolId == STATIC_CAST(TInt, KProtocolInet6RoutingHeader)
		);
	}

// TPacketHead
// ***********

EXPORT_C TBool TPacketHead::ExtHdrGet(TInt aType, TInt& aOfs, TInt& aLen)
	/**
	* Gets the offset and length of an extension header.
	*
	* @param aType Extension header type
	* @retval aOfs On return, the header offset
	* @retval aLen On return, the header length
	* @return ETrue if the header was found, otherwise EFalse 
	*/
	{
	if (iPacket.IsEmpty())
		return EFalse;

	TInt type = ip6.NextHeader();
	TInt ofs = 0;

	while (TPacketPoker::IsExtensionHeader(type) && ofs<iOffset)
		{
		//TInet6Options is about as generic Extension Header class as possible.
		//Thus no need to create another one.
		TInet6Packet<TInet6Options> hdr(iPacket, ofs);
		if (hdr.iHdr == NULL) return EFalse;
		
		if (type == aType)
			{
			aOfs = ofs;
			aLen = hdr.iHdr->HeaderLength();
			return ETrue;
			}
		type = hdr.iHdr->NextHeader();
		ofs += hdr.iHdr->HeaderLength();
		}
	return EFalse;
	}

EXPORT_C TBool TPacketHead::ExtHdrGetOrPrependL(TInt aType, TInt& aOfs, TInt& aLen)
	/**
	* Gets the offset and length of an extension header, or if it doesn't already 
	* exist, creates the header before all other extension headers.
	*
	* @param aType
	*	Extension header type
	* @retval aOfs
	*	If the header is created, this specifies the header offset. If
	*	the header already exists, on return: the actual header offset.
	* @retval aLen
	*	If the header is created, this specifies the header length. If
	*	the header already exists, on return: the actual header length.
	* @return
	*	ETrue if the header was found, EFalse if the header was created.
	*/
	{
	if (ExtHdrGet(aType, aOfs, aLen))
		return ETrue;
	ASSERT(aLen && ((aLen & 7) == 0));

	if (iPacket.IsEmpty())
		{
		iPacket.AllocL(aLen);
		}
	else
		{
		iPacket.PrependL(aLen);
		}
	
	//Zero out the header
	iPacket.FillZ(aLen);
	TInet6Packet<TInet6Options> hdr(iPacket, 0);
	if (hdr.iHdr == NULL)
		{
		iPacket.TrimStart(aLen);
		User::Leave(KErrGeneral);
		}
	
	hdr.iHdr->SetNextHeader(ip6.NextHeader());
	ip6.SetNextHeader(aType);
	hdr.iHdr->SetHdrExtLen((aLen >> 3) - 1);

	aOfs = 0;
	iOffset += aLen;
	return EFalse;
	}

EXPORT_C TBool TPacketHead::ExtHdrGetOrAppendL(TInt aType, TInt& aOfs, TInt& aLen)
	/**
	* Gets the offset and length of an extension header, or if it doesn't already 
	* exist, creates the header after all other extension headers.
	*
	* @param aType	Extension header type
	* @retval aOfs
	*	If the header is created, this specifies the header offset. If
	*	the header already exists, on return, the actual header offset.
	* @retval aLen
	*	If the header is created, this specifies the header length. If
	*	the header already exists, on return, the actual header length.
	* @return ETrue if the header was found, EFalse if the header was created 
	*/
	{
	if (ExtHdrGet(aType, aOfs, aLen)) return ETrue;
	if (iPacket.IsEmpty()) return ExtHdrGetOrPrependL(aType, aOfs, aLen);
	ASSERT(aLen && ((aLen & 7) == 0));
	
	TInt nxt = ip6.NextHeader();
	TInt ofs = 0;
	FOREVER
		{
		TInet6Packet<TInet6Options> hdr(iPacket, ofs);
		if (hdr.iHdr == NULL)
			User::Leave(KErrGeneral);
		
		nxt = hdr.iHdr->NextHeader();
		ofs += hdr.iHdr->HeaderLength();
		if (ofs >= iOffset)
			{
			hdr.iHdr->SetNextHeader(aType);
			break;
			}
		}
	
	RMBufChain tail;
	tail.AllocL(aLen);
	tail.FillZ();
	iPacket.Append(tail);

	TInet6Packet<TInet6Options> hdr(iPacket, iOffset);
	if (hdr.iHdr == NULL)
		{
		iPacket.TrimEnd(iOffset);
		User::Leave(KErrGeneral);
		}
	hdr.iHdr->SetNextHeader(nxt);
	hdr.iHdr->SetHdrExtLen((aLen >> 3) - 1);
	
	aOfs = iOffset;
	iOffset += aLen;
	return EFalse;
	}

//
// TODO: Possibility to replace existing padding with an option which
//       would preserve the alignment. (eg. Home Address is kind of Pad2)
//
EXPORT_C void TPacketHead::AddDestinationOptionL(const TUint8* aOption, TUint8 aLen, TUint8 aAlign/*=0*/, TUint8 aModulo/*=4*/)
	{
	/**
	* Adds a Destination Option extension header option.
	*
	* If no Destination Option extension header already exists, one is created
	* as the first extension header. The option is aligned according to the
	* specified aAlign and aModulo parameters.
	*
	* @param aOption
	*	Option Data
	* @param aLen
	*	Option Data length
	* @param aAlign
	*	Option alignment requirement. The default is no alignment.
	* @param aModulo
	*	Option must start at an octect position which is a multiple 
	*	of the specified value
	*/
	TPtrC8 ptr(aOption, aLen);
	AddDestinationOptionL(ptr, aAlign, aModulo);
	}

EXPORT_C void TPacketHead::AddDestinationOptionL(const TPtrC8& aOption, TUint8 aAlign/*=0*/, TUint8 aModulo/*=4*/)
	/**
	* Adds a Destination Option extension header option.
	*
	* If no Destination Option extension header already exists, one is
	* created as the first extension header. The option is aligned
	* according to the specified aAlign and aModulo parameters.
	*
	* @param aOption
	*	Pointer to the start of the Option Data
	* @param aAlign
	*	Option alignment requirement. The default is no alignment.
	* @param aModulo
	*	Option must start at an octect position which is a multiple
	*	of the specified value
	*/
	{
	TUint padlen = aAlign?(TUint(aAlign - 2) % aModulo):0;  //Leading pad
	TInt len = 2 + padlen + aOption.Length();
	TInt trailpad = (8 - (len & 7)) & 7;
	len += trailpad;

	TInt ofs;
	if (! ExtHdrGetOrAppendL(KProtocolInet6DestinationOptions, ofs, len))
		{
		//Simple case, new header was allocated and initialized with required length.
		//Just put the option in.
		ofs += 2;
#ifdef OPTIMIZE_PADDING
		if (padlen > 1)
			{
			TInet6Packet<TInet6OptionBase> pad(iPacket, ofs);
			if (pad.iHdr == NULL)
				User::Leave(KErrGeneral);
			pad.iHdr->SetType(KDstOptionPadN);
			pad.iHdr->SetDataLen(padlen - 2);
			}
#endif
		ofs += padlen;
		iPacket.CopyIn(aOption, ofs);
#ifdef OPTIMIZE_PADDING
		ofs += aOption.Length();
		if (trailpad > 1)
			{
			TInet6Packet<TInet6OptionBase> pad(iPacket, ofs);
			if (pad.iHdr == NULL)
				User::Leave(KErrGeneral);
			pad.iHdr->SetType(KDstOptionPadN);
			pad.iHdr->SetDataLen(trailpad - 2);
			}
#endif
		return;
		}

	//Now we have the position and size of the header
	TInet6Packet<TInet6Options> opt(iPacket, ofs);
	if (opt.iHdr == NULL)
		User::Leave(KErrGeneral);

	//padlen = leading pad
	ofs += len;
	padlen = aAlign?(TUint(aAlign - ofs) % aModulo):0;
	TInt size = padlen + aOption.Length();
	trailpad = (8 - (size & 7)) & 7;
	size += trailpad;
	
	RMBufChain pkt;
	pkt.AllocL(size);
	pkt.FillZ();
#ifdef OPTIMIZE_PADDING
	//Optimize leading pad
	if (padlen > 1)
		{
		TInet6Packet<TInet6OptionBase> pad(pkt, 0);
		if (pad.iHdr == NULL)
			User::Leave(KErrGeneral);
		pad.iHdr->SetType(KDstOptionPadN);
		pad.iHdr->SetDataLen(padlen - 2);
		}
	//Optimize trailing pad
	if (trailpad > 1)
		{
		TInet6Packet<TInet6OptionBase> pad(pkt, padlen+aOption.Length());
		if (pad.iHdr == NULL)
			User::Leave(KErrGeneral);
		pad.iHdr->SetType(KDstOptionPadN);
		pad.iHdr->SetDataLen(trailpad - 2);
		}
#endif
	pkt.CopyIn(aOption, padlen);
	
	iPacket.Append(pkt);
	
	iOffset += size;
	opt.iHdr->SetHdrExtLen(opt.iHdr->HdrExtLen() + (size >> 3));
	}
