/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 Copyright (c) 1993-1999 Microsoft Corporation

 Module Name:

    unionndr.hxx

 Abstract:

    Contains routines for the generation of the new NDR format strings for
    unions, and the new NDR marshalling and unmarshalling calls.

 Notes:


 History:

    DKays     Nov-1993     Created.
 ----------------------------------------------------------------------------*/

#include "becls.hxx"
#pragma hdrstop

void
CG_ENCAPSULATED_STRUCT::GenNdrFormat( CCB * pCCB )
/*++

Routine Description :

    Generates the format string for an encapsulated union.

Arguments :

    pCCB    - pointer to the code control block

Return :
    
    None.

--*/
{
    FORMAT_STRING *     pFormatString;
    CG_BASETYPE *       pSwitchIsNode;
    CG_UNION *          pUnion;
    unsigned long       SwitchType;

    if ( GetFormatStringOffset() != -1 ) 
        return;

    pFormatString = pCCB->GetFormatString();

    //
    // The child of the struct's first field node is the switch_is node.
    //
    pSwitchIsNode = (CG_BASETYPE *) GetChild()->GetChild();

    //
    // The child of the struct's second field node is the union node.
    //
    pUnion = (CG_UNION *) GetChild()->GetSibling()->GetChild();

    SetFormatStringOffset( pFormatString->GetCurrentOffset() );

    pFormatString->PushFormatChar( FC_ENCAPSULATED_UNION );

    //
    // The switch type field in the format string has the size of the switch_is
    // field (including any needed pading) in the upper four bits and the 
    // actual switch_is type in the lower four bits.
    //

    //
    // Get the amount to increment the memory pointer to the encapsulated
    // union's struct to get to the actual union.  This is the total struct
    // size minus the union's size (this may not simply be the size of the
    // switch_is member because of possible padding).
    //
    CG_FIELD *  pSwitchField;
    CG_FIELD *  pUnionField;

    pSwitchField = (CG_FIELD *) GetChild();
    pUnionField = (CG_FIELD *) pSwitchField->GetSibling();

    //
    // Set the memory increment part of the SwitchType field.
    //
    SwitchType = ( pUnionField->GetMemOffset() - pSwitchField->GetMemOffset() )
                 << 4;

    if ( pSwitchIsNode->GetFormatChar() == FC_ENUM16 ) 
        SwitchType |= FC_ENUM16;
    else
        SwitchType |= pSwitchIsNode->GetSignedFormatChar();

    pFormatString->PushByte( SwitchType );

    pUnion->GenNdrSizeAndArmDescriptions( pCCB );

    SetFormatStringEndOffset( pFormatString->GetCurrentOffset() );
//  SetFormatStringOffset( pFormatString->OptimizeFragment( this ) );

}

void
CG_UNION::GenNdrFormat( CCB * pCCB )
/*++

Routine Description :

    Generates the format string for a non-encapsulated union.

Arguments :

    pCCB    - pointer to the code control block

Return :
    
    None.

--*/
{
    FORMAT_STRING *     pFormatString;
    long                Offset;

    SetCCB( pCCB );

    if ( GetFormatStringOffset() != -1 ) 
        return;

    pFormatString = pCCB->GetFormatString();

    SetFormatStringOffset( pFormatString->GetCurrentOffset() );

    pFormatString->PushFormatChar( FC_NON_ENCAPSULATED_UNION );

    if ( ((CG_BASETYPE *)pCGSwitchType)->GetFormatChar() == FC_ENUM16 ) 
        pFormatString->PushFormatChar( FC_ENUM16 );
    else
        {
        FORMAT_CHARACTER SwitchTypeFc;

        // Note that we take the signed format character this time.

        SwitchTypeFc = ((CG_BASETYPE *)pCGSwitchType)->GetSignedFormatChar();

#if defined(TARGET_RKK)
        if ( pCommand->GetTargetSystem() == NT35  &&
             SwitchTypeFc == FC_USMALL )
            {
            // The NT 807 NDR engine doesn't know about usmall.

            pFormatString->PushFormatChar( FC_BYTE );
            pFormatString->PushFormatChar( FC_SMALL );
            }
        else
#endif

        pFormatString->PushFormatChar( SwitchTypeFc );
        }

    GenNdrSwitchIsDescription( pCCB );

    Offset = pFormatString->GetCurrentOffset();

    pFormatString->PushShortOffset( 0 );

    GenNdrSizeAndArmDescriptions( pCCB );

    pFormatString->PushShortOffset( GetNdrSizeAndArmDescriptionOffset() - Offset,
                                    Offset );

    SetFormatStringEndOffset( pFormatString->GetCurrentOffset() );
//  SetFormatStringOffset( pFormatString->OptimizeFragment( this ) );

}

void
CG_UNION::GenNdrFormatArms( CCB * pCCB )
/*++

Routine Description :

    Generates the format string for the arms of an encapsulated or a
    non-encapsulated union.

Arguments :

    pCCB    - pointer to the code control block

Return :
    
    None.

--*/
{
    CG_ITERATOR Iterator;
    CG_CASE *   pCase;
    CG_NDR *    pNdr;

    GetMembers( Iterator );

    while ( ITERATOR_GETNEXT( Iterator, pCase ) )
        {
        if ( ! pCase->GetChild() ) 
            continue;

        //
        // The child of the CG_CASE is a CG_FIELD.   
        // The child of the CG_FIELD is the actual NDR entity.
        //
        pNdr = (CG_NDR *) pCase->GetChild()->GetChild();

        if ( ! pNdr ) 
            continue;

        if ( pNdr && ( ! pNdr->IsSimpleType() || pNdr->GetRangeAttribute() ) ) 
            pNdr->GenNdrFormat( pCCB );
        }
}

void
CG_UNION::GenNdrSizeAndArmDescriptions( CCB * pCCB )
/*++

Routine Description :

    Generates the memory size and arm description portion of the format 
    string for an encapsulated or a non-encapsulated union.

Arguments :

    pCCB    - pointer to the code control block

Return :
    
    None.

--*/
{
    FORMAT_STRING *     pFormatString;
    unsigned short      UnionArms;
    long                FormatOffset;

    if ( GetNdrSizeAndArmDescriptionOffset() != -1 )
        return;

    pFormatString = pCCB->GetFormatString();

    SetNdrSizeAndArmDescriptionOffset( pFormatString->GetCurrentOffset() );

#ifdef DUMP_UNION_INFO

    FILE * fUnionLog = NULL;

    fUnionLog = fopen("c:\\unioninfo.log", "a+t");

    if (fUnionLog) 
        {
        char *pName = GetSymName();
        unsigned long UnionArms = GetNumberOfArms();
        char *pUnionKind = NULL;
        unsigned long UnionFlavor = GetUnionFlavor();
        char *pFileName = "Unknown";
        char *pEnv = pCommand->Is64BitEnv() ? ("64") : ("32");
 
        switch(UnionFlavor)
        {
            case UNION_UNKNOWN:
                pUnionKind = "Union_Unknown";
                break;
            case UNION_ENCAP:
                pUnionKind = "Union_Encap";
                break;
            case UNION_NONENCAP_DCE:
                pUnionKind = "Union_NonEncap_DCE";
                break;
            case UNION_NONENCAP_MS:
                pUnionKind = "Union_NonEncap_MS";
                break;
            default: 
                pUnionKind = "Unknown";
                break;         
        }

        node_file * pFile = GetType()->GetDefiningFile();
        if (pFile && pFile->GetSymName())
           pFileName = pFile->GetSymName();
 
        fprintf(fUnionLog, "* %s FileName: %s, Symbol: %s, Kind %u(%s), Arms: %u \n",
                pEnv, pFileName, pName, UnionFlavor, pUnionKind, UnionArms); 
        }

#endif

    //
    // Set aside the space for the union's description.  Then we generate
    // the format string description for all the union's arms, and then 
    // we go back and patch up the the union's description to have the 
    // proper offsets.  This must be done to handle self referencing union
    // types.
    //

    // Memory size.
    pFormatString->PushShort( (short) GetMemorySize() );

    //
    // union_arms<2> 
    //
    UnionArms = (unsigned short) GetNumberOfArms();

    if ( GetUnionFlavor() == UNION_NONENCAP_MS )
        {
        // 
        // Microsoft union support.
        // Set the upper four bits of the union_arm<2> field with the 
        // the alignment of the largest aligned union arm.
        //
        UnionArms |= (GetWireAlignment() - 1) << 12;
        }

    pFormatString->PushShort( (short) UnionArms );

    // Get union arms again since we may have just munged it.
    UnionArms = (short) GetNumberOfArms();

    // The arms.
    for ( ; UnionArms-- > 0; ) 
        {
        pFormatString->PushLong( 0 );
        pFormatString->PushShortOffset( 0 );
        }

    // default_arm_description<2>
    pFormatString->PushShortOffset( 0 );

    //
    // Generate the format string descriptions of the arms.
    //
    GenNdrFormatArms( pCCB );

    // Find out where the arms' descriptions begin.
    FormatOffset = GetNdrSizeAndArmDescriptionOffset() + 4;

    CG_ITERATOR         Iterator;
    CG_CASE *           pCase;
    CG_NDR *            pNdr;
    CG_NDR *            pNdrDefaultCase;
    BOOL                DefaultCaseFound;
    
    GetMembers( Iterator );

    pNdrDefaultCase = NULL;
    DefaultCaseFound = FALSE;

    while ( ITERATOR_GETNEXT( Iterator, pCase ) )
        {
        //
        // Check for the default case first.
        //
        if ( pCase->GetCGID() == ID_CG_DEFAULT_CASE )
            {
            pNdrDefaultCase = pCase->GetChild() ? 
                              (CG_NDR *) pCase->GetChild()->GetChild() : 0;

            DefaultCaseFound = TRUE;

#ifdef DUMP_UNION_INFO
  
            if (fUnionLog)
               {
               fprintf(fUnionLog, "DEFAULT\n"); 
               }
#endif

            continue;
            }

        //
        // Fill in the arm's case value.
        //
        if (NULL == pCase->GetExpr())
            {
            RpcError(NULL, 0, NO_CASE_EXPR, GetSymName());
            exit(NO_CASE_EXPR);
            }

#ifdef DUMP_UNION_INFO

        if (fUnionLog)
           fprintf(fUnionLog, "%d \n", (long)pCase->GetExpr()->GetValue());

#endif

        pFormatString->PushLong( (long)pCase->GetExpr()->GetValue(), FormatOffset );
        FormatOffset += 4;

        //
        // Check for a non-default case with an empty (;) arm.
        //
        if ( ! pCase->GetChild() || ! pCase->GetChild()->GetChild() )
            {
            //
            // Increment the FormatOffset past the arm description, which 
            // simply remains zero.
            //
            FormatOffset += 2;
            continue;
            }

        //
        // Else it's a regular case with a valid arm.
        //

        pNdr = (CG_NDR *) pCase->GetChild()->GetChild();

        // Emit a short with type or offset representation.
        // For simple types we push <0x80><type>, for others we push offset<2>.
        // The engine checks if the first byte is 0x80 to decide how it should
        // treat the short, hence the offset range is from 0x8100 to 7fff, i.e.
        // the offset of 0x80xx is invalid.

        if ( pNdr && pNdr->IsSimpleType() )
            {
            short   s;
            //
            // The offset in this case is the actual format character for 
            // the base type, but with a 1 in the upper bit of the short, to 
            // make it look negative.
            //
            s = (short) ((CG_BASETYPE *)pNdr)->GetFormatChar();
            s |= MAGIC_UNION_SHORT;

            pFormatString->PushMagicUnionShort( s, FormatOffset );
            }
        else
            {
            //
            // The offset pushed here is the usual relative offset, except
            // as explained above, it has to be >= 0x8100.
            //
            pFormatString->PushShortOffset( pNdr->GetFormatStringOffset() - 
                                            FormatOffset,
                                            FormatOffset );
            }

        FormatOffset += 2;
        }

    //
    // Finally, handle the default case.
    //
    if ( ! DefaultCaseFound )
        {
        // We push an offset here for easier stub reading as this is an offset.. 
        // However, this would prevent union optimization if we switched it on,
        // so at that stage an FS_ marker is needed to generate a comment.
        //
        pFormatString->PushShortOffset( -1, FormatOffset );
        }
    else
        {
        if ( ! pNdrDefaultCase )
            pFormatString->PushShortOffset( 0, FormatOffset );
        else
            {
            if ( pNdrDefaultCase->IsSimpleType() )
                {
                short s;
                s = (short) ((CG_BASETYPE *)pNdrDefaultCase)->GetFormatChar(); 
                s |= MAGIC_UNION_SHORT;
                pFormatString->PushMagicUnionShort( s, FormatOffset );
                }
            else
                pFormatString->PushShortOffset(
                    pNdrDefaultCase->GetFormatStringOffset() - FormatOffset,
                    FormatOffset );
            }
        }

#ifdef DUMP_UNION_INFO
    if (fUnionLog)
    {
        fprintf(fUnionLog, "+\n");
        fprintf(fUnionLog, "\n");
        fclose(fUnionLog);
    }
#endif

}

BOOL 
CG_UNION::CanUseBuffer()
{
    CG_ITERATOR Iterator;
    CG_CASE *   pCase;
    CG_NDR *    pNdr;
    unsigned long Size;
    long        Align;
    long        TempBufAlign;

    //
    // We will be very strict, since there is not much room for 
    // leeway.  Only return TRUE if all arms have the same size, the same
    // wire alignment, and matching wire/memory alignments & sizes.
    //
    // The real scenario we're after is a union with all pointer arms or
    // longs.  This is fairly common in NT.
    //

    GetMembers( Iterator );

    Size = 0;
    Align = 0;
    
    while ( ITERATOR_GETNEXT( Iterator, pCase ) )
        {
        if ( ! pCase->GetChild() || 
             (pNdr = (CG_NDR *) pCase->GetChild()->GetChild()) == 0 )
            continue;

        TempBufAlign = pNdr->GetWireAlignment();
        
        if ( (pNdr->GetWireSize() != pNdr->GetMemorySize()) ||
             (pNdr->GetMemoryAlignment() != TempBufAlign) )
            return FALSE;

        if ( ! Size ) 
            {
            Size = pNdr->GetWireSize();
            Align = TempBufAlign;
            continue;
            }
            
        if ( (Size != pNdr->GetWireSize()) || (Align != TempBufAlign) )
            return FALSE;
        }

    return TRUE;
}

void
CG_UNION::GenNdrSwitchIsDescription( CCB *  pCCB )
/*++

Routine Description :

    This routine generates the switch_type<1> and switch_is_description<4>
    field for a non-encapsulated union.

Arguments :

    pCCB    - pointer to the code control block

Return :
    
    None.

--*/
{
    CG_NDR *            pParamOrField;
    CG_FIELD *          pField;
    expr_node *     pSwitchExpr;
    BOOL                IsPointer;

    pParamOrField = pCCB->GetLastPlaceholderClass();

    //
    // Get the switch is expression.
    //
    switch ( pParamOrField->GetCGID() )
        {
        case ID_CG_PARAM :
            pSwitchExpr = ((CG_PARAM *)pParamOrField)->GetSwitchExpr();

            // If it's top level param then this flag doesn't matter.
            IsPointer = FALSE;

            break;

        case ID_CG_FIELD :
            pField = (CG_FIELD *) pParamOrField;

            pSwitchExpr = pField->GetSwitchExpr();
    
            // Check if the field is actually a pointer to a union.
            IsPointer = ((CG_NDR *)pField->GetChild())->IsPointer();
                
            break;

        default :
            MIDL_ASSERT(0);
        }

    GenNdrFormatAttributeDescription( pCCB,
                                      NULL,
                                      pSwitchExpr,
                                      IsPointer,
                                      TRUE,
                                      FALSE,
                                      FALSE,
                                      pCommand->IsSwitchDefined( SWITCH_ROBUST ) );
}

void                    
CG_UNION::SetFormatStringOffset( long Offset )
{
    CCB *       pCCB;
    CG_NDR *    pParamOrFieldNode;

    pCCB = GetCCB();

    pParamOrFieldNode = pCCB->GetLastPlaceholderClass();

    if ( pParamOrFieldNode->GetCGID() == ID_CG_PARAM )
        ((CG_PARAM *)pParamOrFieldNode)->SetUnionFormatStringOffset( Offset );
    else
        ((CG_FIELD *)pParamOrFieldNode)->SetUnionFormatStringOffset( Offset );
}

long                    
CG_UNION::GetFormatStringOffset()
{
    CCB *       pCCB;
    CG_NDR *    pParamOrFieldNode;

    pCCB = GetCCB();

    pParamOrFieldNode = pCCB->GetLastPlaceholderClass();

    if ( pParamOrFieldNode->GetCGID() == ID_CG_PARAM )
        return ((CG_PARAM *)pParamOrFieldNode)->GetUnionFormatStringOffset();
    else
        return ((CG_FIELD *)pParamOrFieldNode)->GetUnionFormatStringOffset();
}

void
CG_ENCAPSULATED_STRUCT::GenNdrPointerFixUp( CCB *       pCCB,
                                            CG_STRUCT * pStruct )
{
    //
    // For an encapsulated struct, call this method on the actual union. 
    // Remember that the encap struct's child is a CG_FIELD whose sibling's
    // child will be the actual union.
    //
    ((CG_UNION*)(GetChild()->GetSibling()->GetChild()))->
        GenNdrPointerFixUp( pCCB, pStruct );
}

void
CG_UNION::GenNdrPointerFixUp( CCB *       pCCB,
                              CG_STRUCT * pStruct )
{
    CG_ITERATOR     Iterator;
    CG_NDR *        pMember;
    CG_NDR *        pNdr;
    long            OffsetOffset;

    if ( IsInFixUp() )
        return;

    SetFixUpLock( TRUE );

    OffsetOffset = GetNdrSizeAndArmDescriptionOffset() + 4;

    GetMembers( Iterator );

    while ( ITERATOR_GETNEXT( Iterator, pMember ) )
        {
        if ( ! pMember->GetChild() ||
             ! pMember->GetChild()->GetChild() )
            {
            OffsetOffset += 6;
            continue;
            }

        //
        // Child of the case is a CG_FIELD - get it's child to get the 
        // actual Ndr entity.
        //
        pNdr = (CG_NDR *) pMember->GetChild()->GetChild();

        if ( pNdr == pStruct )
            {
            //
            // Patch up the offset.
            //
            OffsetOffset += 4;

            pCCB->GetFormatString()->PushShortOffset(
                        pStruct->GetFormatStringOffset() - OffsetOffset,
                        OffsetOffset );

            OffsetOffset += 2;
            continue;
            }

        if ( (pNdr->GetCGID() == ID_CG_PTR) ||
             (pNdr->GetCGID() == ID_CG_SIZE_PTR) ||
             (pNdr->GetCGID() == ID_CG_SIZE_LENGTH_PTR) )
            {
            CG_POINTER * pPointer = (CG_POINTER *) pNdr;

            //
            // Check if we're ready for this guy yet.
            //
            if ( pPointer->GetFormatStringOffset() == -1 ) 
                continue;

            // Get the pointee.
            pNdr = (CG_NDR *) pNdr->GetChild();

            //
            // If the pointer's pointee is the struct we're checking for,
            // then patch up the pointer's offset_to_description<2> field.
            //
            if ( pNdr == pStruct )
                {
                long    PointerOffset;

                //
                // Get the offset in the format string where the
                // offset_to_description<2> field of the pointer is.
                //
                PointerOffset = pPointer->GetFormatStringOffset() + 2;

                pCCB->GetFormatString()->PushShortOffset(
                        pStruct->GetFormatStringOffset() - PointerOffset,
                        PointerOffset );

                OffsetOffset += 6;
                continue;
                }
            }

        //
        // Continue the chase if necessary.
        //
        if ( pNdr->IsStruct() || pNdr->IsUnion() || pNdr->IsArray() )
            pNdr->GenNdrPointerFixUp( pCCB, pStruct );

        OffsetOffset += 6;
        }

    SetFixUpLock( FALSE );
}