/*++

Copyright (c) 1998  Microsoft Corporation

Module Name:

    portmgmt.cpp

Abstract:

    Functions for allocating and freeing ports from the Port pool

        PortPoolAllocRTPPort()
	PortPoolFreeRTPPort()

Environment:

    User Mode - Win32

--*/


///////////////////////////////////////////////////////////////////////////////
//                                                                           //
// Functions dealing with the TCP device to reserve/unreserve port ranges.   //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////

 
///////////////////////////////////////////////////////////////////////////////
//                                                                           //
// Include files                                                             //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"


#define NUM_DWORD_BITS (sizeof(DWORD)*8)


///////////////////////////////////////////////////////////////////////////////
//                                                                           //
// Global Variables                                                          //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////

#define NUM_PORTS_PER_RANGE 100

struct	PORT_RANGE
{
	LIST_ENTRY		ListEntry;

    // This is the actual lower port. In case, the range allocated
    // by TCP starts with an odd port, we ignore the first port in
    // which case (low == AllocatedLow + 1). But when we free the
    // port range we should pass in AllocatedLow and not low.
    WORD  AllocatedLow;
    WORD  low;

    // high is the last port we can use and not the allocated high.
    // In some cases high will be one less than the actual allocated high.
    WORD  high;

    //Each bit in this bitmap indicates the status of 2 consecutive ports 

    DWORD *AllocList;
    DWORD dwAllocListSize;
};




class	PORT_POOL :
public	SIMPLE_CRITICAL_SECTION_BASE
{
private:
	HANDLE		TcpDevice;
	LIST_ENTRY	PortRangeList;		// contains PORT_RANGE.ListEntry

private:
	HRESULT		OpenTcpDevice	(void);
	HRESULT		StartLocked		(void);
	void		FreeAll			(void);

	HRESULT	CreatePortRange (
		OUT	PORT_RANGE **	ReturnPortRange);

	HRESULT	ReservePortRange (
		IN  ULONG	RangeLength,
		OUT WORD *	ReturnStartPort);

	HRESULT	UnReservePortRange (
		IN	WORD	StartPort);


public:

	PORT_POOL	(void);
	~PORT_POOL	(void);

	HRESULT		Start	(void);
	void		Stop	(void);

	HRESULT		AllocPort (
		OUT	WORD *	ReturnPort);

	void		FreePort (
		IN	WORD	Port);
};

// global data -------------------------------------------------------------------------

static	PORT_POOL	PortPool;

// extern code -----------------------------------------------------------------------

HRESULT PortPoolStart (void)
{
	return PortPool.Start();
}

void PortPoolStop (void)
{
	PortPool.Stop();
}

HRESULT PortPoolAllocRTPPort (
	OUT	WORD *	ReturnPort)
{
	return PortPool.AllocPort (ReturnPort);
}

HRESULT PortPoolFreeRTPPort (
	IN	WORD	Port)
{
	PortPool.FreePort (Port);

	return S_OK;
}





HRESULT PORT_POOL::ReservePortRange (
	IN  ULONG	RangeLength,
    OUT WORD *	ReturnStartPort)
{
    TCP_BLOCKPORTS_REQUEST	PortRequest;
    DWORD	BytesTransferred;
    ULONG	StartPort;

	AssertLocked();

    *ReturnStartPort = 0;

	if (!TcpDevice) {
		Debug (_T("H323: Cannot allocate port range, TCP device could not be opened.\n"));
		return E_UNEXPECTED;
	}

	assert (TcpDevice != INVALID_HANDLE_VALUE);

    PortRequest.ReservePorts = TRUE;
    PortRequest.NumberofPorts = RangeLength;
    
    if (!DeviceIoControl (TcpDevice, IOCTL_TCP_BLOCK_PORTS,
		&PortRequest, sizeof PortRequest,
		&StartPort, sizeof StartPort, 
		&BytesTransferred, NULL)) {

		DebugLastError (_T("H323: Failed to allocate TCP port range.\n"));
        return GetLastError();
    }

	DebugF (_T("H323: Reserved port range: [%04X - %04X)\n"),
		StartPort, StartPort + PortRequest.NumberofPorts);

    *ReturnStartPort = (WORD) StartPort;
    return S_OK;
}



HRESULT PORT_POOL::UnReservePortRange (
	IN	WORD	StartPort)
{
	TCP_BLOCKPORTS_REQUEST	PortRequest;
	DWORD	BytesTransferred;
	DWORD	Status;

	AssertLocked();

	if (!TcpDevice) {
		Debug (_T("H323: Cannot free TCP port range, TCP device is not open.\n"));
		return E_UNEXPECTED;
	}

	assert (TcpDevice != INVALID_HANDLE_VALUE);

	PortRequest.ReservePorts = FALSE;
	PortRequest.StartHandle = (ULONG) StartPort;
    
	if (!DeviceIoControl(TcpDevice, IOCTL_TCP_BLOCK_PORTS,
		&PortRequest, sizeof PortRequest,
		&Status, sizeof Status,
		&BytesTransferred, NULL)) {

		DebugLastError (_T("H323: Failed to free TCP port range.\n"));

		return GetLastError();
    }

    return S_OK;
}




///////////////////////////////////////////////////////////////////////////////
//                                                                           //
// Port Pool Functions.                                                      //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////



// PORT_POOL -----------------------------------------------------------------------

PORT_POOL::PORT_POOL (void)
{
	TcpDevice = NULL;
	InitializeListHead (&PortRangeList);
}

PORT_POOL::~PORT_POOL (void)
{
	assert (!TcpDevice);
	assert (IsListEmpty (&PortRangeList));
}

HRESULT PORT_POOL::Start (void)
{
	HRESULT		Result;

	Lock();

	Result = OpenTcpDevice();

	Unlock();

	return Result;
}

HRESULT PORT_POOL::OpenTcpDevice (void)
{
    UNICODE_STRING		DeviceName;
    IO_STATUS_BLOCK		IoStatusBlock;
    OBJECT_ATTRIBUTES	ObjectAttributes;
    NTSTATUS			Status;

	if (TcpDevice)
		return S_OK;

    RtlInitUnicodeString (&DeviceName, (PCWSTR) DD_TCP_DEVICE_NAME);

	InitializeObjectAttributes (&ObjectAttributes, &DeviceName,
		OBJ_CASE_INSENSITIVE, NULL, NULL);

    Status = NtCreateFile (
		&TcpDevice,
		SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA ,
		&ObjectAttributes,
		&IoStatusBlock,
		NULL,
		FILE_ATTRIBUTE_NORMAL,
		FILE_SHARE_READ | FILE_SHARE_WRITE,
		FILE_OPEN_IF, 0, NULL, 0);

    if (Status != STATUS_SUCCESS) {
		TcpDevice = NULL;

		DebugError (Status, _T("H323: Failed to open TCP device.\n"));

		return (HRESULT) Status;
    }

    return S_OK;
}

void PORT_POOL::Stop (void)
{
	Lock();

	FreeAll();

	if (TcpDevice) {
		assert (TcpDevice != INVALID_HANDLE_VALUE);

		CloseHandle (TcpDevice);
		TcpDevice = NULL;
	}
    
	Unlock();
}

void PORT_POOL::FreeAll (void)
{
	LIST_ENTRY *	ListEntry;
    PORT_RANGE *	PortRange;

	while (!IsListEmpty (&PortRangeList)) {
		ListEntry = RemoveHeadList (&PortRangeList);
		PortRange = CONTAINING_RECORD (ListEntry, PORT_RANGE, ListEntry);

        // Free the port range PortRange->AllocatedLow
        UnReservePortRange (PortRange -> AllocatedLow);
        EM_FREE (PortRange);
    }
}

/*++

Routine Description:

    This function allocates a pair of RTP/RTCP ports from the 
    port pool.

Arguments:
    
    rRTPport - This is an OUT parameter. If the function succeeds
        rRTPport will contain the RTP port (which is even).
        rRTPport+1 should be used as the RTCP port.
        
Return Values:

    This function returns S_OK on success and E_FAIL if it
    fails to allocate a port range.

--*/

HRESULT PORT_POOL::AllocPort (
	OUT	WORD *	ReturnPort)
{
    DWORD i, j;
    DWORD bitmap = 0x80000000;
	LIST_ENTRY *	ListEntry;
    PORT_RANGE *	PortRange;
	WORD			Port;
	HRESULT			Result;

    Lock();

	for (ListEntry = PortRangeList.Flink; ListEntry != &PortRangeList; ListEntry = ListEntry -> Flink) {
		PortRange = CONTAINING_RECORD (ListEntry, PORT_RANGE, ListEntry);

        for (i = 0; i < PortRange->dwAllocListSize; i++) {

            // traverse through AllocList of this portRange

            if ((PortRange->AllocList[i] & 0xffffffff) != 0xffffffff) {
				// at least one entry is free
				bitmap = 0x80000000;
            
				for (j = 0; j < NUM_DWORD_BITS; j++) {
					// traverse through each bit of the DWORD
					if ((PortRange->AllocList[i] & bitmap) == 0)
					{
						// found a free pair of ports
						Port = (WORD) (PortRange -> low + (i*NUM_DWORD_BITS*2) + (j*2));

						if (Port > PortRange -> high) {
							// This check is needed because the last DWORD
							// in the AllocList may contain bits which are
							// actually not included in the AllocList. 
							goto noports;
						}

						// set the bit to show the pair of ports is allocated
						PortRange -> AllocList[i] |= bitmap;
                    
						// Leave the global critical section for the Port pool 
						Unlock();

						DebugF (_T("H323: Allocated port pair (%04X, %04X).\n"), Port, Port + 1);

						*ReturnPort = Port;

						return S_OK;
					}

					bitmap = bitmap >> 1;
				}
            }
        }
    }
    
noports:
    // CODEWORK: Once we get the new ioctl() for dynamically reserving
    // port ranges, we need to allocate a new port range here. If the
    // ioctl() fails we need to return E_FAIL or another error which
    // says we have run out of ports.

    // Allocate a new port range
    Result = CreatePortRange (&PortRange);

	if (PortRange) {
		InsertHeadList (&PortRangeList, &PortRange -> ListEntry);

		// allocate the first port in the range and 
		Port = PortRange -> low;
		PortRange->AllocList[0] |= 0x80000000;

		DebugF (_T("H323: Allocated port pair (%04X, %04X).\n"),
			Port, Port + 1);

		*ReturnPort = Port;
		Result = S_OK;
	}
	else {
		Debug (_T("H323: Failed to allocate port range.\n"));

		*ReturnPort = 0;
		Result = E_FAIL;
    }

	Unlock();

	return Result;

}


/*++

Routine Description:

    This function frees a pair of RTP/RTCP ports.
    The data structure is changed to show that the pair of ports
    is now available.

    CODEWORK: If an entire port range becomes free, do we release
    the port range to the operating system ? We probably need a
    heuristic to do this because allocating a port range again
    could be an expensive operation.

Arguments:
    
    wRTPport - This gives the RTP port to be freed.
        (RTCP port is RTPport+1 which is implicitly freed because
	 we use one bit store the status of both these ports.)

Return Values:

    Returns S_OK on success or E_FAIL if the port is not found in
    the port pool list.

--*/

void PORT_POOL::FreePort (
	IN	WORD	Port)
{
	HRESULT		Result;

    // assert RTP port is even
    _ASSERTE ((Port & 1) == 0);

    DWORD	Index = 0;
    DWORD	Bitmap = 0x80000000;

	LIST_ENTRY *	ListEntry;
    PORT_RANGE *	PortRange;

	Lock();

	// find the port range that this port belongs to
	// simple linear scan -- suboptimal

	Result = E_FAIL;

	for (ListEntry = PortRangeList.Flink; ListEntry != &PortRangeList; ListEntry = ListEntry -> Flink) {
		PortRange = CONTAINING_RECORD (ListEntry, PORT_RANGE, ListEntry);

		if (PortRange -> low <= Port && PortRange -> high >= Port) {
			Result = S_OK;
			break;
		}
    }
    
	if (Result == S_OK) {
		Index = (Port - PortRange -> low) / (NUM_DWORD_BITS * 2);
    
		// assert index is less than the size of the array
		_ASSERTE (Index < PortRange -> dwAllocListSize);

		// CODEWORK: make sure that the bit is set i.e. the port has
		// been previously allocated. Otherwise return an error and print
		// a warning.
    
		// zero the bit to show the pair of ports is now free

		PortRange -> AllocList [Index] &=
			~(Bitmap >> (((Port - PortRange -> low) / 2) % NUM_DWORD_BITS));
			
		DebugF (_T("H323: Deallocated port pair (%04X, %04X).\n"), Port, Port + 1);
	}
	else {
		DebugF (_T("H323: warning, attempted to free port pair (%04X, %04X), but it did not belong to any port range.\n"),
		        Port, Port + 1);
	}

	Unlock();
}

HRESULT PORT_POOL::CreatePortRange (
	OUT	PORT_RANGE **	ReturnPortRange)
{
    // CODEWORK: Once we get the new ioctl() for dynamically reserving
    // port ranges, we need to allocate a new port range here. If the
    // ioctl() fails we need to return E_FAIL or another error which
    // says we have run out of ports.

    // assert low is even and high is odd
    // _ASSERTE((low % 2) == 0);
    // _ASSERTE((high % 2) == 1);

    HRESULT			Result;
    WORD			AllocatedLowerPort;
    WORD			LowerPort;
    DWORD			NumPortsInRange;
    PORT_RANGE *	PortRange;
	DWORD			dwAllocListSize;

	assert (ReturnPortRange);
	*ReturnPortRange = NULL;

    Result = ReservePortRange (NUM_PORTS_PER_RANGE, &AllocatedLowerPort);
    if (FAILED (Result))
		return Result;

    // If the allocated lower port is odd we do not use the lower port
    // and the range we use starts with the next higher port.
    if ((AllocatedLowerPort & 1) == 1) {
		// the allocated region is ODD
		// don't use the first entry

        NumPortsInRange = NUM_PORTS_PER_RANGE - 1 - ((NUM_PORTS_PER_RANGE) & 1);
        LowerPort       = AllocatedLowerPort + 1;
    }
    else {
		// the allocated region is EVEN
		// don't use the last entry

        NumPortsInRange = NUM_PORTS_PER_RANGE;
        LowerPort       = AllocatedLowerPort;
    }

    // If NumPortsInRange is odd, we can not use the last port
    if ((NumPortsInRange & 1) == 1)
    {
        NumPortsInRange--;
    }
    
    // Each bit gives the status (free/allocated) of two consecutive
    // ports. So, each DWORD can store the status of NUM_DWORD_BITS*2
    // ports. We add (NUM_DWORD_BITS*2 - 1) to round up the number of
    // DWORDS required.
    dwAllocListSize = (NumPortsInRange + NUM_DWORD_BITS*2 - 1)
		/ (NUM_DWORD_BITS * 2);

    // allocate space for the AllocList also
    // Since we do not anticipate too many port ranges being allocated,
    // we do not require a separate heap for these structures.
	PortRange = (PORT_RANGE *) EM_MALLOC (
		sizeof (PORT_RANGE) + dwAllocListSize * sizeof (DWORD));

    if (PortRange == NULL) {
		Debug (_T("H323: Allocation failure, cannot allocate PORT_RANGE and associated bit map\n"));

		UnReservePortRange (AllocatedLowerPort);
        return E_OUTOFMEMORY;
    }

    _ASSERTE((LowerPort + NumPortsInRange - 1) <= 0xFFFF);

    PortRange -> AllocatedLow = AllocatedLowerPort;
    PortRange -> low = LowerPort;
    PortRange -> high = (WORD) (LowerPort + NumPortsInRange - 1);
    PortRange -> dwAllocListSize = dwAllocListSize;
    PortRange -> AllocList = (DWORD *) (PortRange + 1);

	DebugF (_T("H323: Allocated port block: [%04X - %04X].\n"),
		PortRange -> low,
		PortRange -> high,
		PortRange -> dwAllocListSize);
 
   // Initialize the AllocList to show all the ports are free
    ZeroMemory (PortRange -> AllocList, (PortRange -> dwAllocListSize) * sizeof (DWORD));

	*ReturnPortRange = PortRange;
	return S_OK;
}