/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Copyright (c) 1993 Microsoft Corporation

Module Name :

    fullptr.c

Abstract :

    This file contains the APIs for handling full pointers in the NDR engine
    and inlined stubs for MIDL 2.0.

Author :

    David Kays  dkays   January 1994.

Revision History :

  ---------------------------------------------------------------------*/

#include "ndrp.h"

static void 
NdrFullPointerXlatRealloc ( PFULL_PTR_XLAT_TABLES pXlatTables, ulong RefId );


PFULL_PTR_XLAT_TABLES RPC_ENTRY
NdrFullPointerXlatInit( 
    ulong       NumberOfPointers,
    XLAT_SIDE   XlatSide )
/***

Routine description :

    Allocates full pointer support data structures for an RPC call.

Arguments :

    NumberOfPointer - If possible, the stub passes in the total number
                      of possible full pointers that will be used during a call.

Return value :

    A pointer to the full pointer translations tables used during an rpc call.

 ***/
{
    PFULL_PTR_XLAT_TABLES   pXlatTables;
    uint                    RefIdToPointerEntries;
    uint                    PointerToRefIdBuckets;
    BOOL                    fOutOfMemory = FALSE;

    pXlatTables = (PFULL_PTR_XLAT_TABLES) 
                  I_RpcAllocate(sizeof(FULL_PTR_XLAT_TABLES));

    // Because old compilers didn't initialize the xlat ptr in stubmsg
    // at the client in Os mode, we cannot raise exception right away.

    if ( ! pXlatTables )
        fOutOfMemory = TRUE;
    else
        {
        //
        // Determine the size of both translation tables.
        //
        if ( NumberOfPointers )
            {
            ulong       Shift, Bitmask;
    
            RefIdToPointerEntries = (uint) NumberOfPointers;
    
            //
            // Find the smallest power of 2 which is greater than
            // RefIdToPointerEntries.
            //
            for ( Shift = 0, Bitmask = 0x80000000;
                  ! (Bitmask & (ulong) RefIdToPointerEntries);
                  Shift++, Bitmask >>= 1 )
                ;
    
            PointerToRefIdBuckets = (uint)((0xffffffff >> Shift) + 1);
            }
        else
            {
            RefIdToPointerEntries = DEFAULT_REF_ID_TO_POINTER_TABLE_ELEMENTS;
            PointerToRefIdBuckets = DEFAULT_POINTER_TO_REF_ID_TABLE_BUCKETS;
            }
    
        // Make sure we can clean up correctly later if case of an exception.
    
        pXlatTables->RefIdToPointer.XlatTable  = 0;
        pXlatTables->RefIdToPointer.StateTable = 0;
        pXlatTables->PointerToRefId.XlatTable  = 0;
        pXlatTables->RefIdToPointer.NumberOfEntries = 0;
        pXlatTables->PointerToRefId.NumberOfBuckets = 0;
    
        //
        // Initialize the ref id to pointer tables.
        //
        pXlatTables->RefIdToPointer.XlatTable =
            (void **) I_RpcAllocate( RefIdToPointerEntries * sizeof(void *) );
    
        if ( ! pXlatTables->RefIdToPointer.XlatTable )
            fOutOfMemory = TRUE;
        else
            {
            MIDL_memset( pXlatTables->RefIdToPointer.XlatTable,
                         0,
                         RefIdToPointerEntries * sizeof(void *) );
    
            pXlatTables->RefIdToPointer.StateTable =
                (uchar *) I_RpcAllocate( RefIdToPointerEntries * sizeof(uchar) );
        
            if ( ! pXlatTables->RefIdToPointer.StateTable )
                fOutOfMemory = TRUE;
            else
                {
                MIDL_memset( pXlatTables->RefIdToPointer.StateTable,
                             0,
                             RefIdToPointerEntries * sizeof(uchar) );
        
                pXlatTables->RefIdToPointer.NumberOfEntries = RefIdToPointerEntries;
            
                //
                // Intialize the pointer to ref id tables.
                //
                pXlatTables->PointerToRefId.XlatTable =
                    (PFULL_PTR_TO_REFID_ELEMENT *) 
                    I_RpcAllocate( PointerToRefIdBuckets * 
                                   sizeof(PFULL_PTR_TO_REFID_ELEMENT) );
            
                if ( ! pXlatTables->PointerToRefId.XlatTable )
                    fOutOfMemory = TRUE;
                else
                    {
                    MIDL_memset( pXlatTables->PointerToRefId.XlatTable,
                                 0,
                                 PointerToRefIdBuckets * sizeof(PFULL_PTR_TO_REFID_ELEMENT) );
                    }
                }
            }
        }

    if ( fOutOfMemory )
        {
        NdrFullPointerXlatFree( pXlatTables );

        if ( XlatSide == XLAT_SERVER )
            RpcRaiseException( RPC_S_OUT_OF_MEMORY );
        else
            {
            // The old compilers generate a code for the client side that
            // doesn't initialize the table pointer and the stub msg
            // initialization is after call to xlat initialize.
            // So, we are down to checking the pointer when used.

            return( 0 );
            }
        }

    pXlatTables->PointerToRefId.NumberOfBuckets = PointerToRefIdBuckets;
    pXlatTables->PointerToRefId.HashMask = PointerToRefIdBuckets - 1;

    pXlatTables->NextRefId = 1;

    pXlatTables->XlatSide = XlatSide;

    return pXlatTables;
}


void RPC_ENTRY
NdrFullPointerXlatFree( 
    PFULL_PTR_XLAT_TABLES pXlatTables )
/*** 

Routine description :

    Free the full pointer support data structures for an rpc call.

Arguments :

    pXlatTables        - Full pointer translation tables data structure to free.

 ***/
{
    PFULL_PTR_TO_REFID_ELEMENT *    HashTable;
    PFULL_PTR_TO_REFID_ELEMENT      pElement, pTemp;
    ulong                           Buckets;
    ulong                           i;

    if ( ! pXlatTables )
        return;

    //
    // Free the ref id to pointer tables.
    //
    if ( pXlatTables->RefIdToPointer.XlatTable )
        I_RpcFree( pXlatTables->RefIdToPointer.XlatTable );

    if ( pXlatTables->RefIdToPointer.StateTable )
        I_RpcFree( pXlatTables->RefIdToPointer.StateTable );

    //
    // Free the pointer to ref id table.
    //
    HashTable = pXlatTables->PointerToRefId.XlatTable;

    if ( HashTable )
        {
        Buckets = pXlatTables->PointerToRefId.NumberOfBuckets;
    
        for ( i = 0; i < Buckets; i++ )
            if ( HashTable[i] )
                for ( pElement = HashTable[i]; pElement; pElement = pTemp )
                    {
                    pTemp = pElement->Next;
    
                    I_RpcFree(pElement);
                    }
    
        I_RpcFree( HashTable );
        }

    //
    // Free the translation table structure.
    //
    I_RpcFree( pXlatTables );
}


int RPC_ENTRY
NdrFullPointerQueryPointer( 
    PFULL_PTR_XLAT_TABLES   pXlatTables,
    void *                  pPointer,
    uchar                   QueryType,
    ulong *                 pRefId )
/***

Routine description :

    This routine checks if a full pointer is in the full pointer translation 
    table and is marked with the given state.

    If there is no current translation for this pointer then a translation is 
    inserted in the given translation table and a ref id is assigned if pRefId
    is non-null.  

Arguments :

    pXlatTable  - The full pointer translation tables.
    pPointer    - The pointer to check.
    QueryType   - The type of query, either FULL_POINTER_MARSHALLED or 
                    FULL_POINTER_BUF_SIZED.
    pRefId      - The ref id for the pointer is returned if this parameter 
                    is non-null.

Return Value :

    TRUE if the given pointer was null or a translation for the full pointer 
    was found and had the QueryType set, FALSE otherwise.

    A return value of FALSE indicates that the pointee should be sized or 
    marshalled.

 ***/
{
    PFULL_PTR_TO_REFID_ELEMENT *    HashTable;
    PFULL_PTR_TO_REFID_ELEMENT      pElement;
    ulong                           HashTableIndex;

    if ( ! pPointer ) 
        {
        if ( pRefId )
            *pRefId = 0;

        return TRUE;
        }

    if ( ! pXlatTables )
        RpcRaiseException( RPC_S_OUT_OF_MEMORY );

    HashTable = pXlatTables->PointerToRefId.XlatTable;

    //
    // Lookup pPointer in PointerToRefId table.
    //

    HashTableIndex = PTR_HASH( pPointer, pXlatTables->PointerToRefId.HashMask );

    for (  pElement = HashTable[HashTableIndex]; 
           pElement;  
           pElement = pElement->Next ) 
        {
        if ( pElement->Pointer == pPointer ) 
            {
            if ( CHECK_FULL_POINTER_STATE( pElement->State, QueryType ) ) 
                {
                if ( pRefId )
                    *pRefId = pElement->RefId;

                return TRUE;
                }
            else 
                {
                //
                // Assign a ref id now if it doesn't have one and pRefId is 
                // non null.
                //
                if ( pRefId && ! pElement->RefId ) 
                    pElement->RefId = pXlatTables->NextRefId++;

                SET_FULL_POINTER_STATE( pElement->State, QueryType ); 
                }
            break;
            }
        }

    //
    // If there is no translation for the pointer then insert a new one.
    //
    if ( ! pElement ) 
        {
        pElement = (PFULL_PTR_TO_REFID_ELEMENT) 
                   I_RpcAllocate( sizeof(FULL_PTR_TO_REFID_ELEMENT) );

        if ( ! pElement )
            RpcRaiseException( RPC_S_OUT_OF_MEMORY );

        pElement->State = 0;

        SET_FULL_POINTER_STATE( pElement->State, QueryType );

        pElement->Pointer = pPointer;

        if ( pRefId )
            pElement->RefId = pXlatTables->NextRefId++;
        else
            pElement->RefId = 0;

        pElement->Next = HashTable[HashTableIndex];
        HashTable[HashTableIndex] = pElement;
        }

    //
    // Set the ref id return value.
    //
    if ( pRefId )
        {
        //
        // We get here the first time we marshall a new full pointer.
        //
        *pRefId = pElement->RefId;

        //
        // We insert the reverse translation if we're on the client side.
        //
        if ( pXlatTables->XlatSide == XLAT_CLIENT )
            {
            if ( *pRefId >= pXlatTables->RefIdToPointer.NumberOfEntries ) 
                NdrFullPointerXlatRealloc( pXlatTables, *pRefId );

            pXlatTables->RefIdToPointer.XlatTable[*pRefId] = pPointer;
            }
        }

    return FALSE;
}


int RPC_ENTRY
NdrFullPointerQueryRefId( 
    PFULL_PTR_XLAT_TABLES   pXlatTables,
    ulong                   RefId,
    uchar                   QueryType,
    void **                 ppPointer )
/***

Routine description :

    This routine checks if a ref id is in the full pointer translation table
    and is marked with the given state.

    If a translation is found and ppPointer is non-null then *ppPointer is set
    to the value of the ref id's pointer value.

    If this routine returns FALSE for a FULL_POINTER_UNMARSHALLED query then 
    the full pointer should be allocated and a call to 
    NdrFullPointerInsertRefId must be made. 

    If this routine returns FALSE for a FULL_POINTER_MEM_SIZED query then 
    the size of the full pointer's pointee should be added to the current 
    memory sizing calculation.

Arguments :

    pXlatTable   - The full pointer translation tables.
    pRefId       - The ref id to search for.
    QueryType    - The type of query, either FULL_POINTER_UNMARSHALLED or
                    FULL_POINTER_MEM_SIZED.
    ppPointer    - Holds the returned pointer value for the ref id if it is 
                    found.

Return Value :

    TRUE if the ref id is 0 or a translation for the ref id is found and had 
    the QueryType set, FALSE otherwise.

    A return value of FALSE indicates that the pointee should be sized or 
    unmarshalled.

 ***/
{
    uchar *     StateTable;
    void *      Pointer;

    if ( ! RefId ) 
        {
        return TRUE;
        }

    if ( ! pXlatTables )
        RpcRaiseException( RPC_S_OUT_OF_MEMORY );

    if ( RefId >= pXlatTables->NextRefId )
        pXlatTables->NextRefId = RefId + 1;

    if ( RefId >= pXlatTables->RefIdToPointer.NumberOfEntries ) 
        NdrFullPointerXlatRealloc( pXlatTables, RefId );

    StateTable = pXlatTables->RefIdToPointer.StateTable;

    Pointer = pXlatTables->RefIdToPointer.XlatTable[RefId];

    //
    // We make this check first.  It's possible that we will already have
    // a translation for the ref id, but it simply won't be marked yet with
    // the proper state.  In this case we still want to return the correct
    // pointer value from the translation tables, but we'll still end up 
    // returning false when we make the state check.
    //
    if ( ppPointer )
        {
        // 
        // We have to make sure and always copy the pointer's value from
        // the table.  This way we handle the case when a pointer's VALUE 
        // changes to a pointer which has a currently valid RefId (an 
        // example of this could happen when a server swaps the values of 
        // two [in,out] double pointers).
        //
        // Pointer will be null if this is the first time we've seen or used
        // this ref id.
        //
        *ppPointer = Pointer;
        }

    if ( CHECK_FULL_POINTER_STATE( StateTable[RefId], QueryType ) )
        {
        return TRUE;
        }
    else
        {
        SET_FULL_POINTER_STATE( StateTable[RefId], QueryType );
    
        return FALSE;
        }
}


void RPC_ENTRY
NdrFullPointerInsertRefId( 
    PFULL_PTR_XLAT_TABLES     pXlatTables,
    ulong                     RefId,
    void *                    pPointer )
/***

Routine description :

    This routine inserts a ref id to pointer translation in the full pointer
    translation table.

Arguments :

    pXlatTable    - The full pointer translation tables.
    pRefId        - The ref id. 
    pPointer      - The pointer. 

Return Value :

    None.

 ***/
{
    // unused: uchar *        StateTable;

    if ( ! pXlatTables )
        RpcRaiseException( RPC_S_OUT_OF_MEMORY );

    // 
    // Ref id should be non-zero.
    //
    NDR_ASSERT( RefId, "NdrFullPointerInsertRefId : Ref id is zero" ); 

    //
    // Ref id should fit in the current table.
    //
    NDR_ASSERT( RefId < pXlatTables->RefIdToPointer.NumberOfEntries,
                "NdrFullPointerInsertRefId : Ref Id too large" );

    //
    // There should currently be no pointer translation for this ref id.
    //
    NDR_ASSERT( ! pXlatTables->RefIdToPointer.XlatTable[RefId],
                "NdrFullPointerInsertRefId : Translation already exists" );

    //
    // Insert the translation.
    //
    pXlatTables->RefIdToPointer.XlatTable[RefId] = pPointer;

    //
    // If we're on the server side then insert the inverse translation.
    //
    if ( pXlatTables->XlatSide == XLAT_SERVER )
        {
        PFULL_PTR_TO_REFID_ELEMENT  pElement;
        long                        HashTableIndex;

        pElement = (PFULL_PTR_TO_REFID_ELEMENT) 
                   I_RpcAllocate( sizeof(FULL_PTR_TO_REFID_ELEMENT) );

        if ( ! pElement )
            RpcRaiseException( RPC_S_OUT_OF_MEMORY );

        pElement->State = 0;
        pElement->Pointer = pPointer;
        pElement->RefId = RefId;

        HashTableIndex = PTR_HASH( pPointer, 
                                   pXlatTables->PointerToRefId.HashMask );

        pElement->Next = pXlatTables->PointerToRefId.XlatTable[HashTableIndex];
        pXlatTables->PointerToRefId.XlatTable[HashTableIndex] = pElement;
        }
}


int RPC_ENTRY
NdrFullPointerFree( 
    PFULL_PTR_XLAT_TABLES     pXlatTables,
    void *                    pPointer )
/*** 

Routine description :

    Removes a full pointer from the translation tables, and frees it's 
    associated translation data.

Return value :

    TRUE if the pointer has not yet been freed or is null, FALSE otherwise.

 ***/
{
    PFULL_PTR_TO_REFID_ELEMENT *    HashTable;
    PFULL_PTR_TO_REFID_ELEMENT      pElement; 
    ulong                           HashTableIndex;

    if ( ! pPointer ) 
         return FALSE;

    if ( ! pXlatTables )
        RpcRaiseException( RPC_S_OUT_OF_MEMORY );

    HashTable = pXlatTables->PointerToRefId.XlatTable;

    //
    // Lookup pPointer in PointerToRefId table.
    //

    HashTableIndex = PTR_HASH( pPointer, pXlatTables->PointerToRefId.HashMask );

    for ( pElement = HashTable[HashTableIndex]; 
          pElement;   
          pElement = pElement->Next ) 
        {
        if ( pElement->Pointer == pPointer ) 
            {
            if ( CHECK_FULL_POINTER_STATE(pElement->State,FULL_POINTER_FREED) )
                {
                return FALSE;
                }
            else
                {
                SET_FULL_POINTER_STATE(pElement->State,FULL_POINTER_FREED);
                return TRUE;
                }
            }
        }

    //
    // There is an instance when a full pointer is encountered for the first
    // time during freeing.  This occurs if an [in] full pointer is changed
    // by the server manager routine.  If this occurs we must insert the new
    // full pointer so we can keep track of it so that we don't free it more
    // than once.
    //

    pElement = (PFULL_PTR_TO_REFID_ELEMENT)
               I_RpcAllocate( sizeof(FULL_PTR_TO_REFID_ELEMENT) );

    if ( ! pElement )
        RpcRaiseException( RPC_S_OUT_OF_MEMORY );

    pElement->State = 0;
    pElement->Pointer = pPointer;

    HashTableIndex = PTR_HASH( pPointer,
                               pXlatTables->PointerToRefId.HashMask );

    pElement->Next = pXlatTables->PointerToRefId.XlatTable[HashTableIndex];
    pXlatTables->PointerToRefId.XlatTable[HashTableIndex] = pElement;

    SET_FULL_POINTER_STATE( pElement->State, FULL_POINTER_FREED );

    return TRUE;
}


static void
NdrFullPointerXlatRealloc( 
    PFULL_PTR_XLAT_TABLES pXlatTables,
    ulong   RefId)
{
    void *  pMemory;
    uint    Entries;

    if ( ! pXlatTables )
        RpcRaiseException( RPC_S_OUT_OF_MEMORY );

    Entries = (uint) pXlatTables->RefIdToPointer.NumberOfEntries;

    // If the number is much larger than the previous one, most likely the
    // number is invalid. Before we have a concrete solution to replace the
    // current fixed array of RefIdToPointer lookup, this can catch most 
    // of the failures. 
    if ( RefId >= 2 * Entries )
        RpcRaiseException( RPC_X_BAD_STUB_DATA );
    //
    // Realloc RefIdToPointerTable.  Allocate twice as many entries.
    //
    pMemory = I_RpcAllocate( Entries * sizeof(void *) * 2 );

    if ( ! pMemory )
        RpcRaiseException( RPC_S_OUT_OF_MEMORY );

    //
    // Now copy the current entries into the new memory.
    //
    RpcpMemoryCopy( pMemory,
                    pXlatTables->RefIdToPointer.XlatTable,
                    Entries * sizeof( void * ) );

    //
    // Set the new entries to 0.
    //
    MIDL_memset( (char *) pMemory + Entries * sizeof( void *),
                 0,
                 Entries * sizeof( void *) );

    //
    // Free the old table.
    //
    I_RpcFree( pXlatTables->RefIdToPointer.XlatTable );

    //
    // Get the new table.
    //
    pXlatTables->RefIdToPointer.XlatTable = (void**)pMemory;

    //
    // Realloc RefIdToPointerState
    //
    pMemory = I_RpcAllocate( Entries * sizeof(uchar) * 2 );

    if ( ! pMemory )
        RpcRaiseException( RPC_S_OUT_OF_MEMORY );

    //
    // Now copy the current entries into the new memory.
    //
    RpcpMemoryCopy( pMemory,
                    pXlatTables->RefIdToPointer.StateTable,
                    Entries * sizeof(uchar) );

    //
    // Set the new entries to 0.
    //
    MIDL_memset( (char *) pMemory + Entries * sizeof(uchar),
                 0,
                 Entries * sizeof(uchar) );

    //
    // Free the old table.
    //
    I_RpcFree( pXlatTables->RefIdToPointer.StateTable );

    //
    // Get the new table.
    //
    pXlatTables->RefIdToPointer.StateTable = (uchar*)pMemory;

    //
    // Update number of entries.
    //
    pXlatTables->RefIdToPointer.NumberOfEntries *= 2;
}