//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// This source code is licensed under Microsoft Shared Source License
// Version 1.0 for Windows CE.
// For a copy of the license visit http://go.microsoft.com/fwlink/?LinkId=3223.
//


//  Include Files

#include "windows.h"
#include "cclib.h"
#include "memory.h"
#include "cxport.h"

#include "ndis.h"
#include "ndiswan.h"
#include "tcpip.h"
#include "vjcomp.h"

// PPP Include Files

#include "protocol.h"
#include "ppp.h"
#include "lcp.h"
#include "ipcp.h"
#include "ncp.h"
#include "ccp.h"
#include "mac.h"
#include "ip_intf.h"
#include "ras.h"
#include "util.h"

#include "layerfsm.h"
#include "ipv6cp.h"
#include "ipv6intf.h"

static const BYTE g_IFIDZero[IPV6_IFID_LENGTH] = {0};

#define UNIVERSAL_LOCAL_BITNUM	1

//
// Utility macros for treating a byte array as an array of bits
//
#define BITARRAY_SET_BIT(array, bit) (array)[(bit) / 8] |=  (1 << ((bit) % 8)) 
#define BITARRAY_CLR_BIT(array, bit) (array)[(bit) / 8] &= ~(1 << ((bit) % 8))
#define BITARRAY_INV_BIT(array, bit) (array)[(bit) / 8] ^=  (1 << ((bit) % 8))

void
EUIToIFID(
	IN	PBYTE	pEUI,
	IN	DWORD	cbEUI,
	OUT	PBYTE	pIFID)
//
//	Convert a 48 or 64 bit IEEE EUI into an IPV6 IFID using the algorithm
//	specified in RFC2472.
//
{
	if (cbEUI == 8)
	{
		// The only transformation from an EUI-64 is to invert the "u" bit.
		memcpy(pIFID, pEUI, 8);
		BITARRAY_INV_BIT(pIFID, UNIVERSAL_LOCAL_BITNUM);
	}
	else if (cbEUI == 6)
	{
		//
		// EUI-48 is handled by inserting 0xFFFE into the middle to convert
		// it to an EUI-64.
		//
		pIFID[0] = pEUI[0];
		pIFID[1] = pEUI[1];
		pIFID[2] = pEUI[2];
		pIFID[3] = 0xFF;
		pIFID[4] = 0xFE;
		pIFID[5] = pEUI[3];
		pIFID[6] = pEUI[4];
		pIFID[7] = pEUI[5];
		BITARRAY_INV_BIT(pIFID, UNIVERSAL_LOCAL_BITNUM);
	}
	else
	{
		ASSERT(FALSE);
	}
}

BOOL
IFIDEqual(
	IN	const	PBYTE	pIFID1,
	IN	const	PBYTE	pIFID2)
//
//	Compare 2 IPV6 Interface Identifiers.
//	Return TRUE if they are equal, FALSE otherwise.
//
{
	return memcmp(pIFID1, pIFID2, IPV6_IFID_LENGTH) == 0;
}

BOOL
IFIDIsZero(
	IN	const	PBYTE	pIFID1)
//
//	Return TRUE if the IFID is all 0.
//
{
	return IFIDEqual(pIFID1, (const PBYTE)&g_IFIDZero[0]);
}

void
ipv6cpGetUniqueIFID(
	PIPV6Context pContext,
	OUT	PBYTE	 pIFID,
	IN	PBYTE	 pExistingLinkIFID OPTIONAL)
//
//	As per RFC2472 Section 4.1, try to obtain a unique interface identifier
//	to be used for the PPP connection.
//
//	1. If an EUI-48 or EUI-64 is available anywhere on the node, it is used
//	   to generate the tentative IFID.
//  2. Otherwise, a different source of uniqueness should be used such as
//	   a machine serial number.
//  3. Otherwise, a random number should be generated.
//	4. Otherwise, zero can be used which requests the peer to generate the
//     Unique IFID.
//
{
	BOOL	bFoundIFID = FALSE;
	DWORD   method = pContext->NextIFIDMethod;

	ASSERT(method <= METHOD_IFID_RANDOM);

	while (!bFoundIFID)
	{
		if ((method < METHOD_IFID_RANDOM)
		&&  (pContext->dwOptionFlags & (1 << method)))
		{
			// This method is disabled, proceed to next
			method++;
			continue;
		}

		if (method == METHOD_IFID_REGISTRY
		&&  pContext->bRegistryIFIDPresent)
		{
			//  Try the fixed registry setting
			memcpy(pIFID, &pContext->RegistryIFID[0], IPV6_IFID_LENGTH);
		}
		if (method == METHOD_IFID_PERSISTENT_RANDOM
		&&  pContext->bRegistryRandomIFIDPresent)
		{
			//  Use the random number generated in an earlier negotiation
			memcpy(pIFID, &pContext->RegistryRandomIFID[0], IPV6_IFID_LENGTH);
		}
		else if (method == METHOD_IFID_EUI
		     &&  pContext->cbEUI)
		{
			//	Try to produce a unique IFID on the link from an EUI-48 or 64 on the node
			EUIToIFID(&pContext->EUI[0], pContext->cbEUI, pIFID);
		}
		else if (method == METHOD_IFID_DEVICE_ID
		     &&  pContext->cbDeviceID)
		{
			//  Try to produce a unique IFID on the link from Device ID info
			ASSERT(pContext->cbDeviceID == IPV6_IFID_LENGTH);
			memcpy(pIFID, &pContext->DeviceID[0], IPV6_IFID_LENGTH);
			BITARRAY_CLR_BIT(pIFID, UNIVERSAL_LOCAL_BITNUM);
		}
		else if (method == METHOD_IFID_RANDOM)
		{
			// Generate a random number for the IFID
			CeGenRandom(IPV6_IFID_LENGTH, pIFID);

			// The "u" bit of the IFID MUST be set to 0.
			BITARRAY_CLR_BIT(pIFID, UNIVERSAL_LOCAL_BITNUM);
		}
		else
		{
			// Current method not available, on to the next
			method++;
			continue;
		}

		if ((pExistingLinkIFID && IFIDEqual(pIFID, pExistingLinkIFID))
		||  IFIDIsZero(pIFID))
		{
			// The EUI we just generated is identical to another one
			// on the link, or else is 0, so cannot be used.
			// Try the next method (or just keep doing random numbers)
			if (method < METHOD_IFID_RANDOM)
				method++;
		}
		else
		{
			bFoundIFID = TRUE;
		}
	}

	if (method == METHOD_IFID_RANDOM
	&&  pExistingLinkIFID == NULL
	&&  !(pContext->dwOptionFlags & OPTION_DISABLE_PERSISTENT_RANDOM)
	&&  !pContext->bRegistryIFIDPresent)
	{
		// Save the newly generated random IFID in the registry so that it will
		// be used on subsequent connections.
		(void)WriteRegistryValues(HKEY_LOCAL_MACHINE, TEXT("Comm\\ppp\\Parms"),
										RAS_VALUENAME_IPV6_RANDOM_IFID, REG_BINARY, 0, pIFID, IPV6_IFID_LENGTH,
										NULL);
	}

	//
	// To prevent looping, each method (other than random) can only be used once
	// per negotiation.
	//
	if (method < METHOD_IFID_RANDOM)
		method++;
	pContext->NextIFIDMethod = method;
}

void
ipv6cpOptionValueReset(
	PIPV6Context pContext)
//
//	Initialize the option states at the beginning of an
//	IPV6CP negotiation.
//
{
    DEBUGMSG(ZONE_FUNCTION, (TEXT( "pppIpv6cp_OptValueInit\n" )));

	// We want to negotiate a local interface-identifier

	pContext->NextIFIDMethod = 0;

	// Set the initial value for the local interface-identifier
	ipv6cpGetUniqueIFID(pContext, &pContext->LocalInterfaceIdentifier[0], NULL);

	// We allow the peer to negotiate its interface-identifier
	// Set it to 0 prior to the peer assigning it a value.
	memset(pContext->PeerInterfaceIdentifier, 0, 8);

}

DWORD
ipv6cpRequestIFIDCb(
	IN	OUT	PVOID context,
	IN	OUT	POptionInfo pInfo,
		OUT	PBYTE		pCode,
	IN	OUT	PBYTE		*ppOptData,
	IN	OUT	PDWORD		pcbOptData)
//
//	Called when we receive an IPV6CP configure request containing
//	the Interface-Identifier option.  The peer is telling us what
//	its Interface-Identifier (the lower 64 bits of the 128 bit IPv6
//	address) is.
//
{
	PIPV6Context pContext = (PIPV6Context)context;
	DWORD	     dwResult = NO_ERROR;
	PBYTE		 ifidPeerRequested = *ppOptData;

    DEBUGMSG(ZONE_FUNCTION, (TEXT( "ipv6cpRequestInterfaceIdentifierCB\n" )));

	// Length checking of the option should have been done by Option module.
	ASSERT(!ifidPeerRequested || *pcbOptData == IPV6_IFID_LENGTH);

    if (ifidPeerRequested == NULL)
	{
		//
		// The peer did not send this option in a configure request, and
		// we require or want him to send it.  We need to specify the value
		// to NAK with.  We will NAK with a zero IFID to suggest that the peer
		// send this in its next configure-request.
		//
		*ppOptData = (PBYTE)&g_IFIDZero[0];
		*pcbOptData = IPV6_IFID_LENGTH;
	}
	else // Peer sent a config-req with an interface identfier value
	{
		if (IFIDIsZero(ifidPeerRequested))
		{
			//
			//	The Interface-Identifier is 0, the peer is requesting that
			//	we generate a unique Interface-Identifier and NAK with that value.
			//

			if (IFIDIsZero(pContext->LocalInterfaceIdentifier) == 0)
			{
				//
				//	RFC 2472 Section 4.1
				//	"If the two Interface-Identifiers are equal to zero, the Interface-
				//	Identifiers negotiation MUST be terminated by transmitting the Configure-
				//	Reject with the Interface-Identifier value set to 0. In this case
				//	a unique Interface-Identifier can not be negotiated."
				//
				*pCode = PPP_CONFIGURE_REJ;
			}
			else
			{
				//
				// RFC 2472 Section 4.1
				// "If the two Interface-Identifiers are different but the received
				// Identifier is zero, a Configure-Nak is sent with a non-zero Interface-
				// Identifier value suggested for use by the remote peer. Such a suggested
				// Interface-Identifier MUST be different from the Interface-Identifier of
				// the last configure request sent to the peer."
				//
				// Generate a Interface-Identifier for the peer to use.
				//
				ipv6cpGetUniqueIFID(pContext, pContext->IFIDSuggestedToPeer, pContext->LocalInterfaceIdentifier);
				*ppOptData = pContext->IFIDSuggestedToPeer;
				*pCode = PPP_CONFIGURE_NAK;
			}
		}
		else // Peer wants to use a non-zero identifier
		{
			if (IFIDEqual(ifidPeerRequested, pContext->LocalInterfaceIdentifier))
			{
				//
				//	RFC 2472 Section 4.1
				//  "If the two Interface-Identifiers are equal and are not zero,
				//  a Configure-Nak MUST be sent specifying a different non-zero
				//  Interface-Identifier value."
				//
				ipv6cpGetUniqueIFID(pContext, pContext->IFIDSuggestedToPeer, pContext->LocalInterfaceIdentifier);
				*ppOptData = pContext->IFIDSuggestedToPeer;
				*pCode = PPP_CONFIGURE_NAK;
			}
			else
			{
				//
				//	RFC 2472 Section 4.1
				//  "If the two Interface-Identifiers are different and the received
				//  Interface-Identifier is not zero, the Interface-Identifier MUST
				//  be acknowledged."
				//
				memcpy(pContext->PeerInterfaceIdentifier, ifidPeerRequested, IPV6_IFID_LENGTH);
				*pCode = PPP_CONFIGURE_ACK;
			}
		}
	}

	return dwResult;
}

DWORD
ipv6cpBuildIFIDOptionCb(
	IN		PVOID  context,
	IN	OUT	POptionInfo pInfo,
	OUT		PBYTE  pOptData,
	OUT		PDWORD pcbOptData
	)
//
//	Called to fill in the data for an Interface-Identifier option
//	that is part of a configure-request we will send to the peer.
//
{
	PIPV6Context pContext = (PIPV6Context)context;
	DWORD	     dwResult = NO_ERROR;

	memcpy(pOptData, pContext->LocalInterfaceIdentifier, IPV6_IFID_LENGTH);
	*pcbOptData = IPV6_IFID_LENGTH;

	return dwResult;
}

DWORD
ipv6cpAckIFIDOptionCb(
	IN		PVOID  context,
	IN	OUT	POptionInfo pInfo,
	IN		PBYTE  pOptData,
	IN		DWORD  cbOptData
	)
//
//	Called when the peer ACKs the Interface-Identifier option
//	that we sent in a CR.  Since we both now agree on the IFID,
//	we can commit to using it.
//
{
	PIPV6Context pContext = (PIPV6Context)context;
	DWORD	     dwResult = NO_ERROR;

	return dwResult;
}

DWORD
ipv6cpNakIFIDOptionCb(
	IN		PVOID  context,
	IN OUT	struct OptionInfo *pInfo,
	IN		PBYTE  pOptData,
	IN		DWORD  cbOptData
	)
//
//	Called when the peer sends us a configure-nak for an Interface-Identifer option
//	to suggest an Interface-Identifier that we should use.
//
{
	PIPV6Context pContext = (PIPV6Context)context;
	DWORD		 dwResult = NO_ERROR;

	// Length should have been checked by the generic Option code already.
	ASSERT(cbOptData == IPV6_IFID_LENGTH);

	if (!IFIDIsZero(pOptData))
	{
		// Use the IFID suggested by the peer
		memcpy(&pContext->LocalInterfaceIdentifier[0], pOptData, IPV6_IFID_LENGTH);
	}
	else
	{
		DEBUGMSG(ZONE_WARN, (TEXT("PPP: IPv6CP - WARNING - Peer NAK IFID option with IFID=0\n")));
	}

	return dwResult;
}

OptionDescriptor ipv6cpIFIDOptionDescriptor =
{
	IPV6CP_OPT_INTERFACE_IDENTIFIER,	// type == Interface-Identifier
	IPV6_IFID_LENGTH,					// fixedLength
	"IFID",								// name
	ipv6cpBuildIFIDOptionCb,
	ipv6cpAckIFIDOptionCb,
	ipv6cpNakIFIDOptionCb,
	NULL,								// no special reject handling
	ipv6cpRequestIFIDCb,
	NULL								// use default debug hex output
};

DWORD
ipv6cpOptionInit(
	PIPV6Context pContext)
{
	DWORD dwResult;

	dwResult = PppFsmOptionsAdd(pContext->pFsm,
							&ipv6cpIFIDOptionDescriptor,ORL_Wanted, ORL_Wanted,
							NULL);

	return dwResult;
}