/*++

Copyright (c) 1997 - 98, Microsoft Corporation

Module Name:

    rtmnhop.c

Abstract:

    Contains routines for managing RTM Next Hops.

Author:

    Chaitanya Kodeboyina (chaitk)   21-Aug-1998

Revision History:

--*/

#include "pchrtm.h"

#pragma hdrstop


DWORD
WINAPI
RtmAddNextHop (
    IN      RTM_ENTITY_HANDLE               RtmRegHandle,
    IN      PRTM_NEXTHOP_INFO               NextHopInfo,
    IN OUT  PRTM_NEXTHOP_HANDLE             NextHopHandle OPTIONAL,
    OUT     PRTM_NEXTHOP_CHANGE_FLAGS       ChangeFlags
    )

/*++

    Adds or Updates a next hop entry to the entity's next-hop table.

    If the 'nexthop handle' argument is present, then this next-hop
    is updated. Otherwise a search is made for the address in the
    input 'nexthop info', and if a next-hop is found, it is updated.
    If no matching next-hop is found, the a new next-hop is added.

Arguments:

    RtmRegHandle      - RTM registration handle for calling entity,

    NextHopInfo       - Info that corresponds to this next-hop,

    NextHopHandle     - Handle to the next-hop to update is passed 
                        in (or NULL), and if a next-hop is created 
                        a handle to this new next-hop is returned.

    ChangeFlags       - Flags whether this was a add or an update.

Return Value:

    Status of the operation

--*/

{
    PRTM_NET_ADDRESS  NextHopAddress;
    PENTITY_INFO      Entity;
    PDEST_INFO        Dest;
    PNEXTHOP_LIST     NewHopList;
    PNEXTHOP_INFO     NewNextHop;
    PNEXTHOP_INFO     NextHop;
    LOOKUP_CONTEXT    Context;
    PLIST_ENTRY       p;
    DWORD             Status;

    //
    // Validate incoming information before attempting an add
    //

    VALIDATE_ENTITY_HANDLE(RtmRegHandle, &Entity);

    if (NextHopInfo->RemoteNextHop)
    {
        VALIDATE_DEST_HANDLE(NextHopInfo->RemoteNextHop, &Dest);
    }

    //
    // If there is a next hop handle, we can avoid a search
    //

    NextHop = NULL;

    if (ARGUMENT_PRESENT(NextHopHandle) && (*NextHopHandle))
    {
        VALIDATE_NEXTHOP_HANDLE(*NextHopHandle, &NextHop);

        // Make sure that the caller owns this nexthop
        if (NextHop->NextHopInfo.NextHopOwner != RtmRegHandle)
        {
            return ERROR_ACCESS_DENIED;
        }
    }

#if WRN
    NewNextHop = NULL;
    NewHopList = NULL;
#endif

    *ChangeFlags = 0;

    ACQUIRE_NHOP_TABLE_WRITE_LOCK(Entity);

    do
    {
        //
        // Search for the next hop if we don't already have one
        //

        if (NextHop == NULL)
        {
            Status = FindNextHop(Entity, NextHopInfo, &Context, &p);

            if (SUCCESS(Status))
            {
                // The next hop already exists in the tree

                NextHop = CONTAINING_RECORD(p, NEXTHOP_INFO, NextHopsLE);
            }
            else
            {
                // Init new allocations in case we fail in between

                NewNextHop = NULL;
                NewHopList = NULL;

                //
                // Create a new next hop with the input information
                //

                Status = CreateNextHop(Entity, NextHopInfo, &NewNextHop);

                if (!SUCCESS(Status))
                {
                    break;
                }

                //
                // Do we need to create a new list of next hops too ?
                //

                if (p == NULL)
                {
                    NewHopList = AllocNZeroMemory(sizeof(NEXTHOP_LIST));

                    if (NewHopList == NULL)
                    {
                        break;
                    }

                    InitializeListHead(&NewHopList->NextHopsList);

                    // Insert the next-hop-list into the tree

                    NextHopAddress = &NextHopInfo->NextHopAddress;

                    Status = InsertIntoTable(Entity->NextHopTable,
                                             NextHopAddress->NumBits,
                                             NextHopAddress->AddrBits,
                                             &Context,
                                             &NewHopList->LookupLinkage);
                    if (!SUCCESS(Status))
                    {
                        break;
                    }

                    p = &NewHopList->NextHopsList;
                }

                // Insert the next hop in the list and ref it
                InsertTailList(p, &NewNextHop->NextHopsLE);

                Entity->NumNextHops++;

                NextHop = NewNextHop;

                *ChangeFlags = RTM_NEXTHOP_CHANGE_NEW;
            }
        }

        //
        // If this is an update, copy necessary information
        //

        if (*ChangeFlags != RTM_NEXTHOP_CHANGE_NEW)
        {
            CopyToNextHop(Entity, NextHopInfo, NextHop);
        }

        //
        // Return the next hop handle if not passed in
        //

        if (ARGUMENT_PRESENT(NextHopHandle))
        {
            if (*NextHopHandle == NULL)
            {
                *NextHopHandle = MAKE_HANDLE_FROM_POINTER(NextHop);

                REFERENCE_NEXTHOP(NextHop, HANDLE_REF);
            }
        }

        Status = NO_ERROR;
    }
    while(FALSE);

    RELEASE_NHOP_TABLE_WRITE_LOCK(Entity);

    if (!SUCCESS(Status))
    {
        // Some error occured - clean up

        if (NewHopList)
        {
            FreeMemory(NewHopList);
        }

        if (NewNextHop)
        {
            DEREFERENCE_NEXTHOP(NewNextHop, CREATION_REF);
        }
    }

    return Status;
}


DWORD
WINAPI
RtmDeleteNextHop (
    IN      RTM_ENTITY_HANDLE               RtmRegHandle,
    IN      RTM_NEXTHOP_HANDLE              NextHopHandle OPTIONAL,
    IN      PRTM_NEXTHOP_INFO               NextHopInfo
    )

/*++

Routine Description:

    Deletes a next hop from the next-hop table. The next-hop
    memory remains in use until all reference counts go to 0.
    
Arguments:

    RtmRegHandle      - RTM registration handle for calling entity,

    NextHopHandle     - Handle to the next-hop we want to delete,

    NextHopInfo       - If no NextHopHandle is passed in, this is
                        used to match the next-hop to be deleted.

Return Value:

    Status of the operation

--*/

{
    PRTM_NET_ADDRESS  NextHopAddress;
    PLOOKUP_LINKAGE   Linkage;
    PENTITY_INFO      Entity;
    PNEXTHOP_LIST     HopList;
    PNEXTHOP_INFO     NextHop;
    PLOOKUP_CONTEXT   PContext;
    LOOKUP_CONTEXT    Context;
    PLIST_ENTRY       p;
    DWORD             Status;

    VALIDATE_ENTITY_HANDLE(RtmRegHandle, &Entity);

    //
    // If there is a next hop handle, we can avoid a search
    //

    NextHop = NULL;

    if (ARGUMENT_PRESENT(NextHopHandle))
    {
        VALIDATE_NEXTHOP_HANDLE(NextHopHandle, &NextHop);

        // Make sure that the caller owns this nexthop
        if (NextHop->NextHopInfo.NextHopOwner != RtmRegHandle)
        {
            return ERROR_ACCESS_DENIED;
        }
    }

#if WRN
    Status = ERROR_GEN_FAILURE;
#endif

    ACQUIRE_NHOP_TABLE_WRITE_LOCK(Entity);

    do
    {
        //
        // Search for the next hop if we don't already have one
        //

        if (NextHop == NULL)
        {
            Status = FindNextHop(Entity, 
                                 NextHopInfo, 
                                 &Context, 
                                 &p);

            if (!SUCCESS(Status))
            {
                break;
            }

            PContext = &Context;

            NextHop = CONTAINING_RECORD(p, NEXTHOP_INFO, NextHopsLE);
        }
        else
        {
            // Make sure that it has not already been deleted

            if (NextHop->NextHopInfo.State == RTM_NEXTHOP_STATE_DELETED)
            {
                break;
            }

            PContext = NULL;
        }
         
        // Get a 'possible' list entry that starts the hop list

        HopList = CONTAINING_RECORD(NextHop->NextHopsLE.Blink,
                                    NEXTHOP_LIST,
                                    NextHopsList);

        // Delete this next-hop from the nexthops list

        NextHop->NextHopInfo.State = RTM_NEXTHOP_STATE_DELETED;

        RemoveEntryList(&NextHop->NextHopsLE);
        

        // Do we have any more next hops on this list

        if (IsListEmpty(&HopList->NextHopsList))
        {
            // Remove the hop-list from the next hop table

            NextHopAddress = &NextHop->NextHopInfo.NextHopAddress;

            Status = DeleteFromTable(Entity->NextHopTable,
                                     NextHopAddress->NumBits,
                                     NextHopAddress->AddrBits,
                                     PContext,
                                     &Linkage);

            ASSERT(SUCCESS(Status) && (&HopList->LookupLinkage == Linkage));

            FreeMemory(HopList);
        }

        // Dereference the next-hop that was deleted

        Entity->NumNextHops--;

        DEREFERENCE_NEXTHOP(NextHop, CREATION_REF);

        if (ARGUMENT_PRESENT(NextHopHandle))
        {            
            DEREFERENCE_NEXTHOP(NextHop, HANDLE_REF);
        }

        Status = NO_ERROR;
    }
    while (FALSE);

    RELEASE_NHOP_TABLE_WRITE_LOCK(Entity);

    return Status;
}


DWORD
WINAPI
RtmFindNextHop (
    IN      RTM_ENTITY_HANDLE               RtmRegHandle,
    IN      PRTM_NEXTHOP_INFO               NextHopInfo,
    OUT     PRTM_NEXTHOP_HANDLE             NextHopHandle,
    OUT     PRTM_NEXTHOP_INFO              *NextHopPointer OPTIONAL
    )

/*++

Routine Description:

    Finds a next hop, given its info, in entity's next-hop table.

Arguments:

    RtmRegHandle      - RTM registration handle for calling entity,

    NextHopInfo       - Info for the next-hop we are searching for
                        ( NextHopOwner, NextHopAddress, IfIndex ),

    NextHopHandle     - Handle to next-hop is returned (if found),

    NextHopPointer    - A pointer to the next-hop is returned for
                        fast direct access by the next-hop's owner.

Return Value:

    Status of the operation

--*/

{
    PENTITY_INFO      Entity;
    PNEXTHOP_INFO     NextHop;
    PLIST_ENTRY       p;
    DWORD             Status;

    DBG_VALIDATE_ENTITY_HANDLE(RtmRegHandle, &Entity);

    VALIDATE_ENTITY_HANDLE(NextHopInfo->NextHopOwner, &Entity);
    
    if (ARGUMENT_PRESENT(NextHopPointer))
    {
        // Only the nexthop owner gets a direct ptr
        if (RtmRegHandle != NextHopInfo->NextHopOwner)
        {
            return ERROR_ACCESS_DENIED;
        }
    }

    //
    // Search for the next hop in the next hop table
    //

    ACQUIRE_NHOP_TABLE_READ_LOCK(Entity);

    Status = FindNextHop(Entity, NextHopInfo, NULL, &p);

    if (SUCCESS(Status))
    {
        NextHop = CONTAINING_RECORD(p, NEXTHOP_INFO, NextHopsLE);

        *NextHopHandle = MAKE_HANDLE_FROM_POINTER(NextHop);

        REFERENCE_NEXTHOP(NextHop, HANDLE_REF);

        if (ARGUMENT_PRESENT(NextHopPointer))
        {
            *NextHopPointer = &NextHop->NextHopInfo;
        }
    }

    RELEASE_NHOP_TABLE_READ_LOCK(Entity);

    return Status;
}


DWORD
FindNextHop (
    IN      PENTITY_INFO                    Entity,
    IN      PRTM_NEXTHOP_INFO               NextHopInfo,
    OUT     PLOOKUP_CONTEXT                 Context OPTIONAL,
    OUT     PLIST_ENTRY                    *NextHopLE
    )

/*++

Routine Description:

    Finds a next hop, given its info, in entity's next-hop table.

    This is a helper function that is called by public functions 
    that add, delete or find a next hop in the next hop table.

Arguments:

    Entity            - Entity whose nexthop table we are searching,

    NextHopInfo       - Info for the next-hop we are searching for
                        ( NextHopOwner, NextHopAddress, IfIndex ),

    Context           - Search context for holding list of nexthops,

    NextHopLE         - List entry for the matching nexthop (if found)
                        (or) list entry before which it'll be inserted.

Return Value:

    Status of the operation

--*/

{
    PRTM_NET_ADDRESS  NextHopAddress;
    PNEXTHOP_LIST     NextHopsList;
    PNEXTHOP_INFO     NextHop;
    ULONG             IfIndex;
    PLOOKUP_LINKAGE   Linkage;
    PLIST_ENTRY       NextHops, p;
    DWORD             Status;

    *NextHopLE = NULL;

    //
    // Search for list of next hops, given the address
    //

    NextHopAddress = &NextHopInfo->NextHopAddress;

    Status = SearchInTable(Entity->NextHopTable,
                           NextHopAddress->NumBits,
                           NextHopAddress->AddrBits,
                           Context,
                           &Linkage);

    if (!SUCCESS(Status))
    {
        return Status;
    }

    NextHopsList = CONTAINING_RECORD(Linkage, NEXTHOP_LIST, LookupLinkage);

    //
    // Search for the nexthop with the interface idx
    //

    IfIndex = NextHopInfo->InterfaceIndex;

    NextHops = &NextHopsList->NextHopsList;

#if WRN
    NextHop = NULL;
#endif

    for (p = NextHops->Flink; p != NextHops; p = p->Flink)
    {
        NextHop = CONTAINING_RECORD(p, NEXTHOP_INFO, NextHopsLE);

        if (NextHop->NextHopInfo.InterfaceIndex <= IfIndex)
        {
            break;
        }
    }

    *NextHopLE = p;

    if ((p == NextHops) || (NextHop->NextHopInfo.InterfaceIndex != IfIndex))
    {
        return ERROR_NOT_FOUND;
    }

    return NO_ERROR;
}


DWORD
WINAPI
RtmGetNextHopPointer (
    IN      RTM_ENTITY_HANDLE               RtmRegHandle,
    IN      RTM_NEXTHOP_HANDLE              NextHopHandle,
    OUT     PRTM_NEXTHOP_INFO              *NextHopPointer
    )

/*++

Routine Description:

    Gets a direct pointer to the next-hop for read/write by its owner.

Arguments:

    RtmRegHandle      - RTM registration handle for calling entity,

    NextHopHandle     - Handle to the next-hop whose pointer we want,

    NextHopPointer    - A pointer to the next-hop is returned for
                        fast direct access by the caller, only if
                        the caller is the owner of this next-hop.
                       
Return Value:

    Status of the operation

--*/

{
    PENTITY_INFO      Entity;
    PNEXTHOP_INFO     NextHop;

    DBG_VALIDATE_ENTITY_HANDLE(RtmRegHandle, &Entity);

    VALIDATE_NEXTHOP_HANDLE(NextHopHandle, &NextHop);

    //
    // Return a pointer only if caller owns next-hop
    //

    if (NextHop->NextHopInfo.NextHopOwner != RtmRegHandle)
    {
        return ERROR_ACCESS_DENIED;
    }

    *NextHopPointer = &NextHop->NextHopInfo;

    return NO_ERROR;
}


DWORD
WINAPI
RtmLockNextHop(
    IN      RTM_ENTITY_HANDLE               RtmRegHandle,
    IN      RTM_NEXTHOP_HANDLE              NextHopHandle,
    IN      BOOL                            Exclusive,
    IN      BOOL                            LockNextHop,
    OUT     PRTM_NEXTHOP_INFO              *NextHopPointer OPTIONAL
    )

/*++

Routine Description:

    Locks or Unlocks a next hop. This function is called by the
    next-hop's owner to lock the next-hop before making changes
    directly to the next-hop using a pointer to this next-hop.

Arguments:

    RtmRegHandle      - RTM registration handle for calling entity,

    NextHopHandle     - Handle to the next-hop that we want to lock,

    Exclusive         - TRUE to lock in write mode, else read mode,

    LockNextHop       - Lock nexthop if TRUE, Unlock it if FALSE,

    NextHopPointer    - A pointer to the next-hop is returned for
                        fast direct access by the next hop's owner.
                       
Return Value:

    Status of the operation

--*/

{
    PENTITY_INFO      Entity;
    PNEXTHOP_INFO     NextHop;

    VALIDATE_ENTITY_HANDLE(RtmRegHandle, &Entity);

    VALIDATE_NEXTHOP_HANDLE(NextHopHandle, &NextHop);

    //
    // Lock or unlock only if caller owns next-hop
    //

    if (NextHop->NextHopInfo.NextHopOwner != RtmRegHandle)
    {
        return ERROR_ACCESS_DENIED;
    }

    // Return a direct pointer for use in update

    if (ARGUMENT_PRESENT(NextHopPointer))
    {
        *NextHopPointer = &NextHop->NextHopInfo;
    }

    // Lock or unlock the nexthop as the case may be

    if (LockNextHop)
    {
        if (Exclusive)
        {
            ACQUIRE_NHOP_TABLE_WRITE_LOCK(Entity);
        }
        else
        {
            ACQUIRE_NHOP_TABLE_READ_LOCK(Entity);
        }
    }
    else
    {
        if (Exclusive)
        {
            RELEASE_NHOP_TABLE_WRITE_LOCK(Entity);
        }
        else
        {
            RELEASE_NHOP_TABLE_READ_LOCK(Entity);
        }
    }

    return NO_ERROR;
}