//
// 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.
//
/*****************************************************************************
* 
*
*   @doc EX_RAS
*   snddata.c	Send Data functions
*
*   Date: 4/28/99
*
*/


//  Include Files

#include "windows.h"
#include "ndis.h"
#include "ras.h"
#include "raserror.h"
#include "cxport.h"
#include "protocol.h"
#include "ppp.h"
#include "macp.h"
#include "cclib.h"

BOOLEAN
pppCopyNdisBufferChainToWanPacket(
	PNDIS_BUFFER		pNdisBuf,
	PNDIS_WAN_PACKET	pWanPacket)
//
//	Copy the data in an NDIS buffer chain into the contiguous memory of a WAN packet.
//
{
	BOOLEAN bSuccess = TRUE;
	PBYTE	pDst, pEnd, pSrc;
	UINT	BufferLength;

	pDst = pWanPacket->CurrentBuffer;
	pEnd = pWanPacket->CurrentBuffer + pWanPacket->CurrentLength;

	while (pNdisBuf)
	{
		NdisQueryBuffer(pNdisBuf, &pSrc, &BufferLength);
		if ((pDst + BufferLength) > pEnd)
		{
			DEBUGMSG (ZONE_ERROR, (TEXT("PPP: ERROR - pppCopyNdisPacketToWanPacket: NdisBuf too large\n")));
			bSuccess = FALSE;
			break;
		}
		memcpy (pDst, pSrc, BufferLength);
		pDst += BufferLength;
		NdisGetNextBuffer(pNdisBuf, &pNdisBuf);
	}

	pWanPacket->CurrentLength = pDst - pWanPacket->CurrentBuffer;

	return bSuccess;
}

BOOL
CopyNdisPacketToWanPacket(
	IN		PNDIS_PACKET		pNdisPacket,
	IN	OUT	PNDIS_WAN_PACKET	pWanPacket)
//
//	Copy the packet data from the chain of NDIS_BUFFERs in the NDIS_PACKET
//	to the contiguous memory pointed to by pWanPacket->CurrentBuffer.
//
{
	BOOL			bSuccess = TRUE;
	PNDIS_BUFFER	pNdisBuf;

	NdisQueryPacketFirstBuffer(pNdisPacket, &pNdisBuf);

	bSuccess = pppCopyNdisBufferChainToWanPacket(pNdisBuf, pWanPacket);

	return bSuccess;
}

DWORD
pppSendData(
	IN	void  *session,
	IN	USHORT ProtocolWord,
	IN	PBYTE  pSrcData,
	IN	DWORD  cbSrcData)
//
//	Send a ppp frame from a contiguous data source.
//
//	ProtocolWord is one of the constants:
//		PPP_PROTOCOL_LCP,
//		PPP_PROTOCOL_xxx, etc
//  that specifies which protocol header to prepend to the data.
//
//	pSrcData should point to a properly formatted packet of the form:
//		<code> <id> <len:2 bytes> <body:len-4 bytes>
//  The len field must be in MSB first order!
//
//	The ProtocolWord is not yet attached to the packet, because it
//	may be sent compressed.
//
{
	macCntxt_t			*pMac = ((pppSession_t *)session)->macCntxt;
	PNDIS_WAN_PACKET	pPacket;
	NDIS_STATUS			Status;
	DWORD				dwResult = NO_ERROR;

	ASSERT(cbSrcData >= 4);
	ASSERT((DWORD)((pSrcData[2] << 8) | pSrcData[3]) == cbSrcData);

	pPacket = pppMac_GetPacket (((pppSession_t *)session)->macCntxt);
	if (!pPacket)
	{
		DEBUGMSG( ZONE_ERROR, (TEXT("PPP: ERROR - pppSendData - no Tx Buffers\n")));
	}
	else
	{
		// Leave room for the protocol header
		// (2 bytes for address/control, 2 bytes for protocol id).
		pPacket->CurrentBuffer += 4;
		if (cbSrcData <= pPacket->CurrentLength - 4)
		{
			memcpy (pPacket->CurrentBuffer, pSrcData, cbSrcData);
			pPacket->CurrentLength = cbSrcData;
			Status = pppMacSndData (pMac, ProtocolWord, pPacket);
		}
		else
		{
			ASSERT(FALSE);
		}
	}

	return dwResult;
}

void
pppSendNdisBufferChainViaSLIP(
	PVOID         session,
    PNDIS_BUFFER  pNdisBuffer)
//
//	Send a SLIP packet specified by the chain of NDIS_BUFFERs.
//
{
	macCntxt_t		  *pMac = ((pppSession_t *)session)->macCntxt;
	PNDIS_WAN_PACKET  pPacket;
	NDIS_STATUS		  Status;

    DEBUGMSG( ZONE_PPP | ZONE_FUNCTION, (TEXT("PPP:->pppSendNdisBufferChain\r\n")));

	pPacket = pppMac_GetPacket(pMac);

	if (!pPacket)
	{
		DEBUGMSG( ZONE_ERROR, (TEXT("PPP: ERROR - pppSendNdisBufferChain - no Tx Buffers\n")));
		return;
	}

	if (pppCopyNdisBufferChainToWanPacket(pNdisBuffer, pPacket) == FALSE)
	{
		NdisWanFreePacket (pMac, pPacket);
		return;
	}

	Status = pppMacSndData(pMac, PPP_SLIP_PROTOCOL, pPacket);
	DEBUGMSG (ZONE_ERROR && Status, (TEXT("pppSendNdisBufferChain: Error 0x%X from pppMacSndData\r\n"), Status));

    DEBUGMSG( ZONE_PPP | ZONE_FUNCTION, ( TEXT( "PPP:<-pppSendNdisBufferChain\r\n" )));
}

NDIS_STATUS
pppMacSndData(
	macCntxt_t		*pMac,
	USHORT			 wProtocol,
	PNDIS_WAN_PACKET pPacket)
{
	NDIS_STATUS Status;
	PUCHAR		pData;

	DEBUGMSG (ZONE_FUNCTION, (TEXT("PPP: +pppMacSndData (0x%X, 0x%X, 0x%X)\r\n"),
							   pMac, wProtocol, pPacket));

	if (PPP_SLIP_PROTOCOL != wProtocol)
	{
		pData = pPacket->CurrentBuffer;

		//
		//	Prepend the protocol field, which can be compressed down from
		//	2 bytes to 1 byte if Protocol field compression has been
		//	negotiated and the upper byte of the protocol is 0x00.
		//
		//	Also, per IETF STD51 (PPP) section 6.5:
		//		"The Protocol field is never compressed when sending any LCP
		//		 packet."
		//	However, since the protocol ID for LCP is 0xC021, with the high
		//	byte non-zero, it will never be compressed per the normal
		//  compression rules, so a special case is unnecessary..
		//

		// Lower byte of protocol always sent
		*--pData = (BYTE)wProtocol;

		if ((wProtocol & 0xFF00)
		||  !(pMac->dwSendFramingBits & PPP_COMPRESS_PROTOCOL_FIELD))
		{
			// Upper byte of protocol must be sent - do not compress
			*--pData = (BYTE)((wProtocol & 0xFF00) >> 8);
		}

		//
		//	Prepend the address and control bytes (FF 03) if not compressed.
		//
		//	Also, per IETF STD51 (PPP) section 6.6:
		//		"The Address and Control fields MUST NOT be compressed
		//		when sending any LCP packet."
		//
		//  Also, per PPPoE RFC, the Address and Control fields are never sent
		//  for any PPPoE frame.
		//
		if (!pMac->bIsPPPoE
		&&  ((PPP_PROTOCOL_LCP == wProtocol) ||  !(pMac->dwSendFramingBits & PPP_COMPRESS_ADDRESS_CONTROL)))
		{
			*--pData = PPP_UI;
			*--pData = PPP_ALL_STATIONS;
		}
		
		//
		//	The current pointer must always be between the absolute
		//	start and end of the packet buffer memory.
		//
		ASSERT(pPacket->StartBuffer <= pData && pData < pPacket->EndBuffer);

		//
		//	We must not have intruded into the packet header space reserved for
		//	the MAC layer.
		//
		ASSERT(pPacket->StartBuffer + pMac->WanInfo.HeaderPadding <= pData);

		// Adjust packet size to reflect prepended header
		pPacket->CurrentLength += pPacket->CurrentBuffer - pData;
		pPacket->CurrentBuffer  = pData;

		//
		//  Must not intrude into packet trailer space reserved for MAC.
		//
		ASSERT(pData + pPacket->CurrentLength <= pPacket->EndBuffer - pMac->WanInfo.TailPadding);
	}
	
#ifdef DEBUG
	if (ZONE_TRACE) {
		DEBUGMSG (1, (TEXT("PPP: pppMacSndData: About to send packet (%d):\r\n"), pPacket->CurrentLength));
		DumpMem (pPacket->CurrentBuffer, pPacket->CurrentLength);
	}
#endif

	//
	// Invoke packet debug extension if it is loaded.
	//
	if (g_pfnLogTxNdisWanPacket)
	{
		BOOL bDoSend;

		bDoSend = g_pfnLogTxNdisWanPacket(L"PPP", ((pppSession_t *)pMac->session)->context->AdapterName, L"PPP", pPacket);
		if (!bDoSend)
		{
			NdisWanFreePacket (pMac, pPacket);
			return NDIS_STATUS_SUCCESS;
		}
	}

	pppUnLock(pMac->session);
	WanMiniportSend (&Status, pMac->pAdapter->hAdapter, (NDIS_HANDLE)pMac->hCall, pPacket);
	pppLock(pMac->session);

	// If they returned PENDING then the SendComplete handler will free the packet.
	if (NDIS_STATUS_SUCCESS == Status)
	{
		DEBUGMSG (ZONE_MAC, (TEXT("PPP: pppMacSndData: Success from WanMiniportSend\r\n")));
		NdisWanFreePacket (pMac, pPacket);
	}
	else if (NDIS_STATUS_PENDING != Status)
	{
		DEBUGMSG (ZONE_ERROR, (TEXT("PPP: pppMacSndData: Error 0x%X from WanMiniportSend\r\n"), Status));
		NdisWanFreePacket (pMac, pPacket);
	}
	else
	{
		DEBUGMSG (ZONE_MAC, (TEXT("PPP: pppMacSndData: Status Pending from WanMiniportSend, pPacket=0x%X\r\n"), pPacket));
		Status = NDIS_STATUS_SUCCESS;
	}

	DEBUGMSG (ZONE_FUNCTION, (TEXT("PPP: -pppMacSndData: Status=0x%X\r\n"), Status));
	return Status;
}

typedef struct
{
	void	(*pCallbackFunc)(PVOID, NDIS_WAN_GET_LINK_INFO *, NDIS_STATUS);
	PVOID	pCallbackData;
} GetLinkInfoCallbackData;

void
PppNdisGetLinkInfoCompleteCallback(
	NDIS_REQUEST_BETTER		*pRequest,
	GetLinkInfoCallbackData	*pGetLinkInfoCallbackData,
	NDIS_STATUS				Status)
//
//	This function is called when the miniport driver completes
//	an OID_WAN_GET_LINK_INFO request.
//
{
	DEBUGMSG(ZONE_FUNCTION, (TEXT("PPP: +PppNdisGetLinkInfoCompleteCallback Status=%x\n"), Status));

	//
	// Execute the callback
	//
	pGetLinkInfoCallbackData->pCallbackFunc(
		pGetLinkInfoCallbackData->pCallbackData,
		pRequest->Request.DATA.QUERY_INFORMATION.InformationBuffer,
		Status);
	
	// Free the memory allocated for the request.
	pppFreeMemory(pRequest->Request.DATA.QUERY_INFORMATION.InformationBuffer, sizeof(NDIS_WAN_GET_LINK_INFO));

	// Free the memory allocated for the callback info.
	pppFreeMemory(pGetLinkInfoCallbackData, sizeof(*pGetLinkInfoCallbackData));

	DEBUGMSG(ZONE_FUNCTION, (TEXT("PPP: -PppNdisGetLinkInfoCompleteCallback\n")));
}

void
pppMac_GetLinkInfo(
	IN	PVOID			pvArg,
	IN	void			(*pGetLinkInfoCompleteCallback)(PVOID, NDIS_WAN_GET_LINK_INFO *, NDIS_STATUS),
	IN	PVOID			pCallbackData)
{
	macCntxt_t				 *pMac = pvArg;
	NDIS_STATUS				 Status;
	PNDIS_WAN_GET_LINK_INFO  pLinkInfo;
	GetLinkInfoCallbackData  *pGetLinkInfoCallbackData;

	DEBUGMSG (ZONE_FUNCTION, (TEXT("PPP: +pppMac_GetLinkInfo\n")));

	if ((NDIS_HANDLE)pMac->hCall == INVALID_HANDLE_VALUE)
	{
		//
		//	If the call just got closed by NdisTapiHangup, don't try to
		//	talk to the adapter.
		//
		pGetLinkInfoCompleteCallback(pCallbackData, NULL, NDIS_STATUS_CLOSING);
	}
	else
	{
		pGetLinkInfoCallbackData = pppAllocateMemory(sizeof(*pGetLinkInfoCallbackData));
		if (pGetLinkInfoCallbackData == NULL)
		{
			// Unable to queue the callback
			pGetLinkInfoCompleteCallback(pCallbackData, NULL, NDIS_STATUS_RESOURCES);
		}
		else
		{
			pGetLinkInfoCallbackData->pCallbackFunc = pGetLinkInfoCompleteCallback;
			pGetLinkInfoCallbackData->pCallbackData = pCallbackData;

			//
			//	Issue a new get link info request
			//

			Status = NDIS_STATUS_RESOURCES;

			pLinkInfo = pppAllocateMemory(sizeof(*pLinkInfo));
			if (pLinkInfo)
			{
				pLinkInfo->NdisLinkHandle = (NDIS_HANDLE)pMac->hCall;

				pppUnLock(pMac->session);
				PppNdisIssueRequest(&Status, 
									pMac->pAdapter,
									NdisRequestQueryInformation,
									OID_WAN_GET_LINK_INFO,
									pLinkInfo,
									sizeof(*pLinkInfo),
									pMac,
									PppNdisGetLinkInfoCompleteCallback,
									pGetLinkInfoCallbackData,
									NULL);
				pppLock(pMac->session);
			}
			else
			{
				pppFreeMemory(pGetLinkInfoCallbackData, sizeof(*pGetLinkInfoCallbackData));
				pGetLinkInfoCompleteCallback(pCallbackData, NULL, NDIS_STATUS_RESOURCES);
			}
		}
	}

	DEBUGMSG (ZONE_FUNCTION, (TEXT("PPP: -pppMac_GetLinkInfo\n")));
}

void
PppNdisSetLinkInfoCompleteCallback(
	NDIS_REQUEST_BETTER		*pRequest,
	GetLinkInfoCallbackData	*pGetLinkInfoCallbackData,
	NDIS_STATUS				Status)
//
//	This function is called when the miniport driver completes
//	an OID_WAN_SET_LINK_INFO request.
//
{
	DEBUGMSG(ZONE_FUNCTION, (TEXT("PPP: +PppNdisSetLinkInfoCompleteCallback Status=%x\n"), Status));
	
	// Free the memory allocated for the request.
	pppFreeMemory(pRequest->Request.DATA.SET_INFORMATION.InformationBuffer, sizeof(NDIS_WAN_SET_LINK_INFO));

	DEBUGMSG(ZONE_FUNCTION, (TEXT("PPP: -PppNdisSetLinkInfoCompleteCallback\n")));
}

VOID
pppMac_SetLink(
	IN	macCntxt_t				*pMac,
	IN	PNDIS_WAN_SET_LINK_INFO  pLinkInfo)
{
	PNDIS_WAN_SET_LINK_INFO  pNewLinkInfo;
	NDIS_STATUS		         Status;

	DEBUGMSG (ZONE_FUNCTION, (TEXT("PPP: +pppMac_SetLink\n")));

	pLinkInfo->HeaderPadding = pMac->WanInfo.HeaderPadding;
	pLinkInfo->TailPadding   = pMac->WanInfo.TailPadding;

	//
	//	If the call just got closed by NdisTapiHangup, don't try to
	//	talk to the adapter.
	//
	if ((NDIS_HANDLE)pMac->hCall != INVALID_HANDLE_VALUE)
	{

		DEBUGMSG(ZONE_MAC, (L"PPP: Set SendACCM=%08X RecvACCM=%08X\n",
								pLinkInfo->SendACCM,
								pLinkInfo->RecvACCM));

		//
		//	Issue a new set link info request
		//

		Status = NDIS_STATUS_RESOURCES;

		pNewLinkInfo = pppAllocateMemory(sizeof(*pNewLinkInfo));
		if (pNewLinkInfo)
		{
			pMac->dwSendFramingBits = pLinkInfo->SendFramingBits;
			memcpy(pNewLinkInfo, pLinkInfo, sizeof(*pNewLinkInfo));
			pLinkInfo->NdisLinkHandle = (NDIS_HANDLE)pMac->hCall;
			pNewLinkInfo->NdisLinkHandle = (NDIS_HANDLE)pMac->hCall;

			pppUnLock(pMac->session);
			PppNdisIssueRequest(&Status, 
								pMac->pAdapter,
								NdisRequestSetInformation,
								OID_WAN_SET_LINK_INFO,
								pNewLinkInfo,
								sizeof(*pNewLinkInfo),
								pMac,
								PppNdisSetLinkInfoCompleteCallback,
								pNewLinkInfo,
								NULL);
			pppLock(pMac->session);

		}
	}

	DEBUGMSG (ZONE_FUNCTION, (TEXT("PPP: -pppMac_SetLink\n")));
}

PNDIS_WAN_INFO
pppMacGetWanInfo (macCntxt_t *pMac)
{
	DEBUGMSG (ZONE_FUNCTION, (TEXT("pppMacGetWanInfo(0x%X): About to return 0x%X (MaxFrame=%d)\r\n"),
							  pMac, &(pMac->WanInfo), pMac->WanInfo.MaxFrameSize));
	return &pMac->WanInfo;
}

void
pppMacIssueWanGetStatsInfoCompleteCallback(
	NDIS_REQUEST_BETTER	*pRequest,
	macCntxt_t			*pMac,
	NDIS_STATUS			 Status)
{
	pMac->bMacStatsObtained = Status == NDIS_STATUS_SUCCESS;
}

void
pppMacIssueWanGetStatsInfo(
	macCntxt_t *pMac)
//
//  Send an asynchronous OID_WAN_GET_STATS_INFO request.
//  This is done just prior to closing a line in order to obtain
//  final stats data.
//
{
	NDIS_STATUS		            Status;

	if (pMac->hCall != (ULONG)INVALID_HANDLE_VALUE)
	{
		pMac->MacStats.NdisLinkHandle = (NDIS_HANDLE)pMac->hCall;

		PppNdisIssueRequest(&Status, 
								pMac->pAdapter,
								NdisRequestQueryInformation,
								OID_WAN_GET_STATS_INFO,
								&pMac->MacStats,
								sizeof(pMac->MacStats),
								pMac,
								pppMacIssueWanGetStatsInfoCompleteCallback,
								pMac,
								NULL);
	}
}

NDIS_STATUS
pppMacGetWanStats(
	IN	PVOID						context,
	OUT	PNDIS_WAN_GET_STATS_INFO	pStats)
{
	macCntxt_t *pMac = (macCntxt_t *)context;
	NDIS_STATUS	Status = NDIS_STATUS_SUCCESS;

	//
	// If the line is still open, retrieve the most
	// up to date statistics.
	//
	if (pMac->hCall != (ULONG)INVALID_HANDLE_VALUE)
	{
		pMac->MacStats.NdisLinkHandle = (NDIS_HANDLE)pMac->hCall;
		PppNdisDoSyncRequest(&Status,
							pMac->pAdapter,
							NdisRequestQueryInformation,
							OID_WAN_GET_STATS_INFO,
							&pMac->MacStats,
							sizeof(pMac->MacStats));

		pMac->bMacStatsObtained = Status == NDIS_STATUS_SUCCESS;
	}
	else
	{
		// If MAC stats are supported, but the line has been closed, then
		// we can return the final stats that were retrieved just before
		// the line was closed.
		if (!pMac->bMacStatsObtained)
			Status = NDIS_STATUS_NOT_SUPPORTED;
	}

	*pStats = pMac->MacStats;

	return Status;
}
