#include "precomp.h"
#include <it120app.h>

//
// CMG.C
// Call Management
//
// Copyright(c) Microsoft 1997-
//


#define MLZ_FILE_ZONE  ZONE_NET

//
// CMP_Init()                                              
//
BOOL CMP_Init(BOOL * pfCleanup)
{
    BOOL                rc = FALSE;
    GCCError            gcc_rc;

    DebugEntry(CMP_Init);

    UT_Lock(UTLOCK_T120);

    if (g_putCMG || g_pcmPrimary)
    {
        *pfCleanup = FALSE;
        ERROR_OUT(("Can't start CMP primary task; already running"));
        DC_QUIT;
    }
    else
    {
        *pfCleanup = TRUE;
    }

    //
    // Register CMG task
    //
    if (!UT_InitTask(UTTASK_CMG, &g_putCMG))
    {
        ERROR_OUT(("Failed to start CMG task"));
        DC_QUIT;
    }

    //
    // Allocate a Call Manager handle, ref counted
    //
    g_pcmPrimary = (PCM_PRIMARY)UT_MallocRefCount(sizeof(CM_PRIMARY), TRUE);
    if (!g_pcmPrimary)
    {
        ERROR_OUT(("CMP_Init failed to allocate CM_PRIMARY data"));
        DC_QUIT;
    }

    SET_STAMP(g_pcmPrimary, CMPRIMARY);
    g_pcmPrimary->putTask       = g_putCMG;

    //
    // Init the people list
    //
    COM_BasedListInit(&(g_pcmPrimary->people));

    //
    // Register event and exit procedures
    //
    UT_RegisterExit(g_putCMG, CMPExitProc, g_pcmPrimary);
    g_pcmPrimary->exitProcRegistered = TRUE;

    //                                                                    
    // - GCCCreateSap, which is the interesting one.                       
    //
    gcc_rc = GCC_CreateAppSap((IGCCAppSap **) &(g_pcmPrimary->pIAppSap),
                              g_pcmPrimary,
                              CMPGCCCallback);
    if (GCC_NO_ERROR != gcc_rc || NULL == g_pcmPrimary->pIAppSap)
    {
        ERROR_OUT(( "Error from GCCCreateSap"));
        DC_QUIT;
    }

    rc = TRUE;

DC_EXIT_POINT:
    UT_Unlock(UTLOCK_T120);

    DebugExitBOOL(CMP_Init, rc);
    return(rc);
}



//
// CMP_Term()                                               
//
void CMP_Term(void)
{
    DebugEntry(CMP_Term);

    UT_Lock(UTLOCK_T120);

    if (g_pcmPrimary)
    {
        ValidateCMP(g_pcmPrimary);

        ValidateUTClient(g_putCMG);

        //
        // Unregister our GCC SAP.                                             
        //
        if (NULL != g_pcmPrimary->pIAppSap)
        {
            g_pcmPrimary->pIAppSap->ReleaseInterface();
            g_pcmPrimary->pIAppSap = NULL;
        }

        //
        // Call the exit procedure to do all our termination                   
        //
        CMPExitProc(g_pcmPrimary);
    }

    UT_TermTask(&g_putCMG);

    UT_Unlock(UTLOCK_T120);

    DebugExitVOID(CMP_Term);
}




//
// CMPExitProc()                                     
//
void CALLBACK CMPExitProc(LPVOID data)
{
    PCM_PRIMARY pcmPrimary = (PCM_PRIMARY)data;

    DebugEntry(CMPExitProc);

    UT_Lock(UTLOCK_T120);

    //
    // Check parameters                                                    
    //
    ValidateCMP(pcmPrimary);
    ASSERT(pcmPrimary == g_pcmPrimary);

    //
    // Deregister the exit procedure.
    //
    if (pcmPrimary->exitProcRegistered)
    {
        UT_DeregisterExit(pcmPrimary->putTask,
                          CMPExitProc,
                          pcmPrimary);
        pcmPrimary->exitProcRegistered = FALSE;
    }

    CMPCallEnded(pcmPrimary);

    //
    // Free the CMP data
    //
    UT_FreeRefCount((void**)&g_pcmPrimary, TRUE);

    UT_Unlock(UTLOCK_T120);

    DebugExitVOID(CMPExitProc);

}

//
// CMPCallEnded()                                            
//
void CMPCallEnded
(
    PCM_PRIMARY pcmPrimary
)
{
    PCM_PERSON  pPerson;
    PCM_PERSON  pPersonT;
    int         cmTask;

    DebugEntry(CMPCallEnded);

    ValidateCMP(pcmPrimary);

    if (!(pcmPrimary->currentCall))
    {
        TRACE_OUT(("CMCallEnded: not in call"));
        DC_QUIT;
    }

    //
    // Issue CMS_PERSON_LEFT events for all people still in the call.  
    // Do this back to front.
    //
    pPerson = (PCM_PERSON)COM_BasedListLast(&(pcmPrimary->people), FIELD_OFFSET(CM_PERSON, chain));
    while (pPerson != NULL)
    {
        ASSERT(pcmPrimary->peopleCount > 0);

        TRACE_OUT(("Person [%d] LEAVING call", pPerson->netID));

        //
        // Get the previous person
        //
        pPersonT = (PCM_PERSON)COM_BasedListPrev(&(pcmPrimary->people), pPerson,
                                     FIELD_OFFSET(CM_PERSON, chain));

        //
        // Remove this guy from the list
        //
        COM_BasedListRemove(&(pPerson->chain));
        pcmPrimary->peopleCount--;

        //
        // Notify people of his leaving
        //
        CMPBroadcast(pcmPrimary,
                    CMS_PERSON_LEFT,
                    pcmPrimary->peopleCount,
                    pPerson->netID);

        //
        // Free the memory for the item
        //
        delete pPerson;

        //
        // Move the previous person in the list
        pPerson = pPersonT;
    }

    //
    // Inform all registered secondary tasks of call ending (call          
    // CMbroadcast() with CMS_END_CALL)                                    
    //
    CMPBroadcast(pcmPrimary,
                CMS_END_CALL,
                0,
                pcmPrimary->callID);

    //
    // Reset the current call vars
    //
    pcmPrimary->currentCall  = FALSE;
    pcmPrimary->fTopProvider    = FALSE;
    pcmPrimary->callID          = 0;
    pcmPrimary->gccUserID       = 0;
    pcmPrimary->gccTopProviderID    = 0;

    //
    // Discard outstanding channel/token requests
    //
    for (cmTask = CMTASK_FIRST; cmTask < CMTASK_MAX; cmTask++)
    {
        if (pcmPrimary->tasks[cmTask])
        {
            pcmPrimary->tasks[cmTask]->channelKey = 0;
            pcmPrimary->tasks[cmTask]->tokenKey = 0;
        }
    }

DC_EXIT_POINT:
    //
    // Nobody should be in the call anymore
    //
    ASSERT(pcmPrimary->peopleCount == 0);

    DebugExitVOID(CMCallEnded);
}




//                                                                         
// CMPGCCCallback                                            
//
void CALLBACK CMPGCCCallback(GCCAppSapMsg * gccMessage)
{
    PCM_PRIMARY                         pcmPrimary;
    GCCConferenceID                     confID;
    GCCApplicationRoster FAR * FAR *    pRosterList;
    UINT                                roster;
    LPOSTR                              pOctetString;
    GCCObjectKey FAR *                  pObjectKey;
    UINT                              checkLen;

    DebugEntry(CMPGCCCallback);

    UT_Lock(UTLOCK_T120);

    //
    // The userDefined parameter is the Primary's PCM_CLIENT.               
    //
    pcmPrimary = (PCM_PRIMARY)gccMessage->pAppData;

    if (pcmPrimary != g_pcmPrimary)
    {
        ASSERT(NULL == g_pcmPrimary);
        return;
    }

    ValidateCMP(pcmPrimary);

    switch (gccMessage->eMsgType)
    {
        case GCC_PERMIT_TO_ENROLL_INDICATION:
        {
            //
            // This indicates a conference has started:                    
            //
            CMPProcessPermitToEnroll(pcmPrimary,
                        &gccMessage->AppPermissionToEnrollInd);
        }
        break;

        case GCC_ENROLL_CONFIRM:
        {
            //
            // This contains the result of a GCCApplicationEnrollRequest.  
            //
            CMPProcessEnrollConfirm(pcmPrimary,
                        &gccMessage->AppEnrollConfirm);
        }
        break;

        case GCC_REGISTER_CHANNEL_CONFIRM:
        {
            //
            // This contains the result of a GCCRegisterChannelRequest.    
            //
            CMPProcessRegistryConfirm(
                        pcmPrimary,
                        gccMessage->eMsgType,
                        &gccMessage->RegistryConfirm);
        }
        break;

        case GCC_ASSIGN_TOKEN_CONFIRM:
        {
            //
            // This contains the result of a GCCRegistryAssignTokenRequest.
            //
            CMPProcessRegistryConfirm(
                        pcmPrimary,
                        gccMessage->eMsgType,
                        &gccMessage->RegistryConfirm);
        }
        break;

        case GCC_APP_ROSTER_REPORT_INDICATION:
        {
            //
            // This indicates that the application roster has changed.     
            //
            confID = gccMessage->AppRosterReportInd.nConfID;
            pRosterList = gccMessage->AppRosterReportInd.apAppRosters;

            for (roster = 0;
                 roster < gccMessage->AppRosterReportInd.cRosters;
                 roster++)
            {

                //
                // Check this app roster to see if it relates to the       
                // Groupware session (the first check is because we always 
                // use a NON_STANDARD application key).                    
                //
                pObjectKey = &(pRosterList[roster]->
                               session_key.application_protocol_key);

                //
                // We only ever use a non standard key.                    
                //
                if (pObjectKey->key_type != GCC_H221_NONSTANDARD_KEY)
                {
                    TRACE_OUT(("Standard key, so not a roster we are interested in..."));
                    continue;
                }

                pOctetString = &pObjectKey->h221_non_standard_id;

                //
                // Now check the octet string.  It should be the same      
                // length as our hardcoded GROUPWARE- string (including    
                // NULL term) and should match byte for byte:              
                //
                checkLen = sizeof(GROUPWARE_GCC_APPLICATION_KEY);
                if ((pOctetString->length != checkLen)
                    ||
                    (memcmp(pOctetString->value,
                            GROUPWARE_GCC_APPLICATION_KEY,
                            checkLen) != 0))
                {
                    //
                    // This roster is not for our session - go to the next 
                    // one.                                                
                    //
                    TRACE_OUT(("Roster not for Groupware session - ignore"));
                    continue;
                }

                //
                // Process the application roster.                         
                //
                CMPProcessAppRoster(pcmPrimary,
                                       confID,
                                       pRosterList[roster]);
            }
        }
        break;
    }

    UT_Unlock(UTLOCK_T120);

    DebugExitVOID(CMPGCCCallback);
}


//
//                                                                        
// CMPBuildGCCRegistryKey(...)                                              
//
//
void CMPBuildGCCRegistryKey
(
    UINT                    dcgKeyNum,
    GCCRegistryKey FAR *    pGCCKey,
    LPSTR                   dcgKeyStr
)
{
    DebugEntry(CMPBuildGCCRegistryKey);

    //
    // Build up a string of the form "Groupware-XX" where XX is a string   
    // representation (in decimal) of the <dcgKey> parameter passed in.    
    //
    memcpy(dcgKeyStr, GROUPWARE_GCC_APPLICATION_KEY, sizeof(GROUPWARE_GCC_APPLICATION_KEY)-1);

    wsprintf(dcgKeyStr+sizeof(GROUPWARE_GCC_APPLICATION_KEY)-1, "%d",
        dcgKeyNum);

    //
    // Now build the GCCRegistryKey.  This involves putting a pointer to   
    // our static <dcgKeyStr> deep inside the GCC structure.  We also store
    // the length, which is lstrlen+1, because we want to include the    
    // NULLTERM explicitly (since GCC treats the octet_string as an        
    // arbitrary array of bytes).                                          
    //

    pGCCKey->session_key.application_protocol_key.
        key_type = GCC_H221_NONSTANDARD_KEY;

    pGCCKey->session_key.application_protocol_key.h221_non_standard_id.
        length = sizeof(GROUPWARE_GCC_APPLICATION_KEY);

    pGCCKey->session_key.application_protocol_key.h221_non_standard_id.
        value = (LPBYTE) GROUPWARE_GCC_APPLICATION_KEY;

    pGCCKey->session_key.session_id          = 0;

    pGCCKey->resource_id.length =
              (sizeof(GROUPWARE_GCC_APPLICATION_KEY) +
              lstrlen(&dcgKeyStr[sizeof(GROUPWARE_GCC_APPLICATION_KEY)-1]));

    pGCCKey->resource_id.value               = (LPBYTE) dcgKeyStr;


    DebugExitVOID(CMPBuildGCCRegistryKey);
}



//                                                                        
// CMPProcessPermitToEnroll(...)                                            
//
void CMPProcessPermitToEnroll
(
    PCM_PRIMARY                         pcmPrimary,
    GCCAppPermissionToEnrollInd *       pMsg
)
{
    DebugEntry(CMPProcessPermitToEnroll);

    ValidateCMP(pcmPrimary);

    //
    // We will send CMS_PERSON_JOINED events when we receive a         
    // GCC_APP_ROSTER_REPORT_INDICATION.                                   
    //

    if (pMsg->fPermissionGranted)
    {
        // CALL STARTED

        //
        // If we haven't had a NCS yet then we store the conference ID.    
        // Otherwise ignore it.                                            
        //
        ASSERT(!pcmPrimary->currentCall);

        //
        // Initially, we do not consider ourselves to be in the call - we will 
        // add an entry when we get the ENROLL_CONFIRM:                        
        //
        ASSERT(pcmPrimary->peopleCount == 0);

        pcmPrimary->currentCall = TRUE;
        pcmPrimary->callID      = pMsg->nConfID;
        pcmPrimary->fTopProvider =
            pcmPrimary->pIAppSap->IsThisNodeTopProvider(pMsg->nConfID);

        //
        // Tell GCC whether we're interested:                              
        //
        if (!CMPGCCEnroll(pcmPrimary, pMsg->nConfID, TRUE))
        {
            //
            // We are only interested in an error if it is a Groupware conf.   
            // All we can really do is pretend the conference has ended due
            // to a network error.                                         
            //
            WARNING_OUT(("Error from CMPGCCEnroll"));
            CMPCallEnded(pcmPrimary);
        }

        //
        // The reply will arrive on a GCC_ENROLL_CONFIRM event.            
        //
    }
    else
    {
        // CALL ENDED
        if (g_pcmPrimary->currentCall)
        {
            //
            // Inform Primary task and all secondary tasks that the call has ended 
            //

            CMPCallEnded(g_pcmPrimary);

            //
            // Un-enroll from the GCC Application Roster.                          
            //
            if (g_pcmPrimary->bGCCEnrolled)
            {
                CMPGCCEnroll(g_pcmPrimary, g_pcmPrimary->callID, FALSE);
                g_pcmPrimary->bGCCEnrolled = FALSE;
            }
        }
    }

    DebugExitVOID(CMPProcessPermitToEnroll);
}



//
//                                                                        
// CMPProcessEnrollConfirm(...)                                             
//
//
void CMPProcessEnrollConfirm
(
    PCM_PRIMARY             pcmPrimary,
    GCCAppEnrollConfirm *   pMsg
)
{
    DebugEntry(CMPProcessEnrollConfirm);

    ValidateCMP(pcmPrimary);

    ASSERT(pcmPrimary->currentCall);
    ASSERT(pMsg->nConfID == pcmPrimary->callID);

    //
    // This event contains the GCC node ID (i.e.  the MCS user ID of the   
    // GCC node controller at this node).  Store it for later reference    
    // against the roster report:                                          
    //
    TRACE_OUT(( "GCC user_id: %u", pMsg->nidMyself));

    pcmPrimary->gccUserID           = pMsg->nidMyself;
    pcmPrimary->gccTopProviderID    = pcmPrimary->pIAppSap->GetTopProvider(pcmPrimary->callID);
    ASSERT(pcmPrimary->gccTopProviderID);

    if (pMsg->nResult != GCC_RESULT_SUCCESSFUL)
    {
        WARNING_OUT(( "Attempt to enroll failed (reason: %u", pMsg->nResult));
        //
        // All we can really do is pretend the conference has ended due to 
        // a network error.                                                
        //
        CMPCallEnded(pcmPrimary);
    }

    DebugExitVOID(CMProcessEnrollConfirm);
}



//                                                                        
// CMPProcessRegistryConfirm(...)                                           
//
void CMPProcessRegistryConfirm
(
    PCM_PRIMARY         pcmPrimary,
    GCCMessageType      messageType,
    GCCRegistryConfirm *pConfirm
)
{
    UINT                event =     0;
    BOOL                succeeded;
    LPSTR               pGCCKeyStr;    // extracted from the GCC registry key  
    UINT                dcgKeyNum;     // the value originally passed in as key
    UINT                itemID;        // can be channel or token ID
    int                 cmTask;
    PUT_CLIENT          secondaryHandle = NULL;

    DebugEntry(CMPProcessRegistryConfirm);

    ValidateCMP(pcmPrimary);

    //
    // Check this is for the Groupware conference:                         
    //
    if (!pcmPrimary->currentCall ||
        (pConfirm->nConfID != pcmPrimary->callID))
    {
        WARNING_OUT(( "Got REGISTRY_XXX_CONFIRM for unknown conference %lu",
            pConfirm->nConfID));
        DC_QUIT;
    }

    //
    // Embedded deep down inside the message from GCC is a pointer to an   
    // octet string which is of the form "Groupware-XX", where XX is a     
    // string representation of the numeric key the original Call Manager  
    // secondary used when registering the item.  Extract it now:          
    //
    pGCCKeyStr = (LPSTR)pConfirm->pRegKey->resource_id.value;

    dcgKeyNum = DecimalStringToUINT(&pGCCKeyStr[sizeof(GROUPWARE_GCC_APPLICATION_KEY)-1]);

    if (dcgKeyNum == 0)
    {
        WARNING_OUT(( "Received ASSIGN/REGISTER_CONFIRM with unknown key: %s",
            pGCCKeyStr));
        DC_QUIT;
    }

    TRACE_OUT(( "Conf ID %u, DCG Key %u, result %u",
        pConfirm->nConfID, dcgKeyNum, pConfirm->nResult));

    //
    // This is either a REGISTER_CHANNEL_CONFIRM or a ASSIGN_TOKEN_CONFIRM.
    // Check, and set up the relevant pointers:                            
    //
    switch (messageType)
    {
        case GCC_REGISTER_CHANNEL_CONFIRM:
        {
            event = CMS_CHANNEL_REGISTER_CONFIRM;
            itemID = pConfirm->pRegItem->channel_id;

            // Look for task that registered this channel
            for (cmTask = CMTASK_FIRST; cmTask < CMTASK_MAX; cmTask++)
            {
                if (pcmPrimary->tasks[cmTask] &&
                    (pcmPrimary->tasks[cmTask]->channelKey == dcgKeyNum))
                {
                    pcmPrimary->tasks[cmTask]->channelKey = 0;
                    secondaryHandle = pcmPrimary->tasks[cmTask]->putTask;
                }
            }
        }
        break;

        case GCC_ASSIGN_TOKEN_CONFIRM:
        {
            event = CMS_TOKEN_ASSIGN_CONFIRM;
            itemID = pConfirm->pRegItem->token_id;

            // Look for task that assigned this token
            for (cmTask = CMTASK_FIRST; cmTask < CMTASK_MAX; cmTask++)
            {
                if (pcmPrimary->tasks[cmTask] &&
                    (pcmPrimary->tasks[cmTask]->tokenKey == dcgKeyNum))
                {
                    pcmPrimary->tasks[cmTask]->tokenKey = 0;
                    secondaryHandle = pcmPrimary->tasks[cmTask]->putTask;
                }
            }
        }
        break;

        default:
        {
            ERROR_OUT(( "Unexpected registry event %u", messageType));
            DC_QUIT;
        }
    }

    switch (pConfirm->nResult)
    {
        case GCC_RESULT_SUCCESSFUL:
        {
            //
            // We were the first to register an item against this key.     
            //
            TRACE_OUT(("We were first to register using key %u (itemID: %u)",
                     dcgKeyNum, itemID));
            succeeded = TRUE;
        }
        break;

        case GCC_RESULT_ENTRY_ALREADY_EXISTS:
        {
            //
            // Someone beat us to it: they have registered a channel       
            // against the key we specified.  This value is in the GCC     
            // message:                                                    
            //
            TRACE_OUT(("Another node registered using key %u (itemID: %u)",
                      dcgKeyNum, itemID));
            succeeded = TRUE;
        }
        break;

        default:
        {
            ERROR_OUT(("Error %#hx registering/assigning item against key %u",
                     pConfirm->nResult, dcgKeyNum));
            succeeded = FALSE;
        }
        break;
    }

    //
    // Tell the secondary about the result.                                
    //
    if (secondaryHandle)
    {
        UT_PostEvent(pcmPrimary->putTask,
                 secondaryHandle,
                 0,
                 event,
                 succeeded,
                 MAKELONG(itemID, dcgKeyNum));
    }

DC_EXIT_POINT:
    DebugExitVOID(CMProcessRegistryConfirm);
}



//                                                                        
// CMPProcessAppRoster(...)                                               
//
void CMPProcessAppRoster
(
    PCM_PRIMARY             pcmPrimary,
    GCCConferenceID         confID,
    GCCApplicationRoster*   pAppRoster
)
{
    UINT                    newList;
    UserID                  oldNode;
    UserID                  newNode;
    PCM_PERSON              pPerson;
    PCM_PERSON              pPersonT;
    BOOL                    found;
    int                     task;
    BOOL                    notInOldRoster = TRUE;
    BOOL                    inNewRoster    = FALSE;

    DebugEntry(CMPProcessAppRoster);

    ValidateCMP(pcmPrimary);

    //
    // If we are not in a call ignore this.                                
    //
    if (!pcmPrimary->currentCall ||
        (confID != pcmPrimary->callID))
    {
        WARNING_OUT(("Report not for active Groupware conference - ignore"));
        DC_QUIT;
    }

    //
    // At this point, pAppRoster points to the bit of the roster which     
    // relates to Groupware.  Trace out some info:                         
    //
    TRACE_OUT(( "Number of records %u;", pAppRoster->number_of_records));
    TRACE_OUT(( "Nodes added: %s, removed: %s",
        (pAppRoster->nodes_were_added   ? "YES" : "NO"),
        (pAppRoster->nodes_were_removed ? "YES" : "NO")));

    //
    // We store the GCC user IDs in shared memory as TSHR_PERSONIDs.
    // Compare this list of people we know to be in the call, and 
    //      * Remove people no longer around
    //      * See if we are new to the roster
    //      * Add people who are new
    //

    pPerson = (PCM_PERSON)COM_BasedListFirst(&(pcmPrimary->people), FIELD_OFFSET(CM_PERSON, chain));

    while (pPerson != NULL)
    {
        ASSERT(pcmPrimary->peopleCount > 0);

        oldNode = (UserID)pPerson->netID;

        //
        // Get the next guy in the list in case we remove this one.
        //
        pPersonT = (PCM_PERSON)COM_BasedListNext(&(pcmPrimary->people), pPerson,
                                     FIELD_OFFSET(CM_PERSON, chain));

        //
        // Check to see if our node is currently in the roster             
        // 
        if (oldNode == pcmPrimary->gccUserID)
        {
            TRACE_OUT(( "We are currently in the app roster"));
            notInOldRoster = FALSE;
        }

        //
        // ...check if they're in the new list...                          
        //
        found = FALSE;
        for (newList = 0; newList < pAppRoster->number_of_records; newList++)
        {
            if (oldNode == pAppRoster->application_record_list[newList]->node_id)
            {
                found = TRUE;
                break;
            }
        }

        if (!found)
        {
            //
            // This node is no longer present, so remove him.
            //
            TRACE_OUT(("Person %u left", oldNode));

            COM_BasedListRemove(&(pPerson->chain));
            pcmPrimary->peopleCount--;

            CMPBroadcast(pcmPrimary,
                        CMS_PERSON_LEFT,
                        pcmPrimary->peopleCount,
                        oldNode);

            //
            // Free the memory for the person item
            //
            delete pPerson;
        }

        pPerson = pPersonT;
    }

    //
    // Now see if we are new to the roster
    //
    for (newList = 0; newList < pAppRoster->number_of_records; newList++)
    {
        if (pAppRoster->application_record_list[newList]->node_id ==
                                                   pcmPrimary->gccUserID)
        {
            TRACE_OUT(( "We are in the new app roster"));
            inNewRoster = TRUE;
            break;
        }
    }

    if (notInOldRoster && inNewRoster)
    {
        //
        // We are new to the roster so we can now do all the processing we 
        // were previously doing in the enroll confirm handler.  GCC spec  
        // requires that we do not do this until we get the roster         
        // notification back.                                              
        //                                                                
        // Flag we are enrolled and start registering channels etc.        
        //
        pcmPrimary->bGCCEnrolled = TRUE;

        //
        // Post a CMS_NEW_CALL events to all secondary tasks               
        //
        TRACE_OUT(( "Broadcasting CMS_NEW_CALL with call handle 0x%08lx",
                                        pcmPrimary->callID));

        //
        // If we are not the caller then delay the broadcast a little      
        //
        CMPBroadcast(pcmPrimary, CMS_NEW_CALL,
            pcmPrimary->fTopProvider, pcmPrimary->callID);

#ifdef _DEBUG
        //
        // Process any outstanding channel register and assign token       
        // requests.                                                       
        //
        for (task = CMTASK_FIRST; task < CMTASK_MAX; task++)
        {
            if (pcmPrimary->tasks[task] != NULL)
            {
                ASSERT(pcmPrimary->tasks[task]->channelKey == 0);
                ASSERT(pcmPrimary->tasks[task]->tokenKey == 0);
            }
        }
#endif // _DEBUG
    }

    //
    // If we are not yet enrolled in the conference then do not start      
    // sending PERSON_JOINED notifications.                                
    //
    if (!pcmPrimary->bGCCEnrolled)
    {
        DC_QUIT;
    }

    //
    // Add the new people (this will include us).  At this point, we know 
    // that everyone in the people list is currently in the roster, since
    // we would have removed 'em above.
    //
    // We need to walk the existing list over and over.
    // But at least we can skip the people we add.  So we save the current
    // front of the list.
    //
    pPersonT = (PCM_PERSON)COM_BasedListFirst(&(pcmPrimary->people), FIELD_OFFSET(CM_PERSON, chain));

    for (newList = 0; newList < pAppRoster->number_of_records; newList++)
    {
        newNode = pAppRoster->application_record_list[newList]->node_id;

        found = FALSE;

        pPerson  = pPersonT;

        while (pPerson != NULL)
        {
            if (newNode == pPerson->netID)
            {
                //
                // This person already existed - don't need to do anything 
                //
                found = TRUE;
                break;          // out of inner for loop                   
            }

            pPerson = (PCM_PERSON)COM_BasedListNext(&(pcmPrimary->people), pPerson,
                FIELD_OFFSET(CM_PERSON, chain));
        }

        if (!found)
        {
            //
            // This dude is new; add him to our people list.
            //
            TRACE_OUT(("Person with GCC user_id %u joined", newNode));

            pPerson = new CM_PERSON;
            if (!pPerson)
            {
                //
                // Uh oh; can't add him.
                //
                ERROR_OUT(("Can't add person GCC user_id %u; out of memory",
                    newNode));
                break;
            }

            ZeroMemory(pPerson, sizeof(*pPerson));
            pPerson->netID = newNode;

            //
            // LONCHANC: We should collapse all these events into a single one
            // that summarize all added and removed nodes,
            // instead of posting the events one by one.
            //

            //
            // Stick him in at the beginning.  At least that way we don't
            // have to look at his record anymore.
            //
            COM_BasedListInsertAfter(&(pcmPrimary->people), &pPerson->chain);
            pcmPrimary->peopleCount++;

            CMPBroadcast(pcmPrimary, 
                CMS_PERSON_JOINED,
                pcmPrimary->peopleCount,
                newNode);
        }
    }

    TRACE_OUT(( "Num people now in call %u", pcmPrimary->peopleCount));

DC_EXIT_POINT:
    DebugExitVOID(CMPProcessAppRoster);
}



//
// CMPBroadcast()                                            
//
void CMPBroadcast
(
    PCM_PRIMARY pcmPrimary,
    UINT        event,
    UINT        param1,
    UINT        param2
)
{
    int         task;

    DebugEntry(CMPBroadcast);

    ValidateCMP(pcmPrimary);

    //
    // for every secondary task                                            
    //
    for (task = CMTASK_FIRST; task < CMTASK_MAX; task++)
    {
        if (pcmPrimary->tasks[task] != NULL)
        {
            UT_PostEvent(pcmPrimary->putTask,
                         pcmPrimary->tasks[task]->putTask,
                         NO_DELAY,
                         event,
                         param1,
                         param2);

        }
    }

    DebugExitVOID(CMPBroadcast);
}


//                                                                        
// CMPGCCEnroll(...)                                                        
//                                                                        
BOOL CMPGCCEnroll
(
    PCM_PRIMARY         pcmPrimary,
    GCCConferenceID     conferenceID,
    BOOL                fEnroll
)
{
    GCCError                    rcGCC =         GCC_NO_ERROR;
    GCCSessionKey               gccSessionKey;
    GCCObjectKey FAR *          pGCCObjectKey;
    BOOL                        succeeded = TRUE;
    GCCEnrollRequest            er;
    GCCRequestTag               nReqTag;

    DebugEntry(CMPGCCEnroll);

    ValidateCMP(pcmPrimary);

    //
    // Do some error checking.                                             
    //
    if (fEnroll && pcmPrimary->bGCCEnrolled)
    {
        WARNING_OUT(("Already enrolled"));
        DC_QUIT;
    }

    TRACE_OUT(("CMGCCEnroll for CM_hnd 0x%08x, confID 0x%08x, in/out %d",
                           pcmPrimary, conferenceID, fEnroll));

    //
    // Set up the session key which identifies us uniquely in the GCC      
    // AppRoster.  We use a non-standard key (because we're not part of the
    // T.120 standards series)                                             
    //                                                                    
    // Octet strings aren't null terminated, but we want ours to include   
    // the NULL at the end of the C string, so specify lstrlen+1 for the 
    // length.                                                             
    //
    pGCCObjectKey = &(gccSessionKey.application_protocol_key);

    pGCCObjectKey->key_type = GCC_H221_NONSTANDARD_KEY;

    pGCCObjectKey->h221_non_standard_id.value =
        (LPBYTE) GROUPWARE_GCC_APPLICATION_KEY;
    pGCCObjectKey->h221_non_standard_id.length =
                       sizeof(GROUPWARE_GCC_APPLICATION_KEY);

    gccSessionKey.session_id = 0;

    //
    // Try to enroll/unenroll with GCC.  This may fail because we have not 
    // yet received a GCC_PERMIT_TO_ENROLL_INDICATION.                     

    //
    // Fill in the enroll request structure
    //
    ZeroMemory(&er, sizeof(er));
    er.pSessionKey = &gccSessionKey;
    // er.fEnrollActively = FALSE;
    // er.nUserID = 0; // no user ID
    // er.fConductingCapable = FALSE;
    er.nStartupChannelType = MCS_STATIC_CHANNEL;
    // er.cNonCollapsedCaps = 0;
    // er.apNonCollapsedCaps = NULL;
    // er.cCollapsedCaps = 0;
    // er.apCollapsedCaps = NULL;
    er.fEnroll = fEnroll;

    rcGCC = pcmPrimary->pIAppSap->AppEnroll(
                                   conferenceID,
                                   &er,
                                   &nReqTag);
    if (GCC_NO_ERROR != rcGCC)
    {
        //
        // Leave the decision about any error processing to the caller.    
        //
        TRACE_OUT(("Error 0x%08x from GCCApplicationEnrollRequest conf ID %lu enroll=%s",
              rcGCC, conferenceID, fEnroll ? "YES": "NO"));
        succeeded = FALSE;
    }
    else
    {
        //
        // Whether we have asked to enroll or un-enroll, we act as if we   
        // are no longer enrolled at once.  We are only really enrolled    
        // when we receive an enroll confirm event.                        
        //
        pcmPrimary->bGCCEnrolled = FALSE;
        ASSERT(succeeded);
        TRACE_OUT(( "%s with conference %d", fEnroll ? 
                         "Enroll Outstanding" : "Unenrolled",
               conferenceID));
    }


DC_EXIT_POINT:
    DebugExitBOOL(CMPGCCEnroll, succeeded);
    return(succeeded);
}



//
// CMS_Register()                                           
//
BOOL CMS_Register
(
    PUT_CLIENT      putTask,
    CMTASK          taskType,
    PCM_CLIENT*     ppcmClient
)
{
    BOOL            fRegistered = FALSE;
    PCM_CLIENT      pcmClient = NULL;

    DebugEntry(CMS_Register);

    UT_Lock(UTLOCK_T120);

    if (!g_pcmPrimary)
    {
        ERROR_OUT(("CMS_Register failed; primary doesn't exist"));
        DC_QUIT;
    }

    ValidateUTClient(putTask);

    ASSERT(taskType >= CMTASK_FIRST);
    ASSERT(taskType < CMTASK_MAX);

    *ppcmClient = NULL;

    //
    // Is this task already present?  If so, share it
    //
    if (g_pcmPrimary->tasks[taskType] != NULL)
    {
        TRACE_OUT(("Sharing CMS task 0x%08x", g_pcmPrimary->tasks[taskType]));

        *ppcmClient = g_pcmPrimary->tasks[taskType];
        ValidateCMS(*ppcmClient);

        (*ppcmClient)->useCount++;

        // Return -- we exist.
        fRegistered = TRUE;
        DC_QUIT;
    }

    //
    // If we got here the task is not a Call Manager Secondary yet, so go  
    // ahead with the registration.                                        
    //

    //
    // Allocate memory for the client
    //
    pcmClient = new CM_CLIENT;
    if (! pcmClient)
    {
        ERROR_OUT(("Could not allocate CM handle"));
        DC_QUIT;
    }
    ZeroMemory(pcmClient, sizeof(*pcmClient));
    *ppcmClient = pcmClient;

    //
    // Fill in information                                                 
    //
    SET_STAMP(pcmClient, CMCLIENT);
    pcmClient->putTask      = putTask;
    pcmClient->taskType     = taskType;
    pcmClient->useCount     = 1;

    UT_BumpUpRefCount(g_pcmPrimary);
    g_pcmPrimary->tasks[taskType] = pcmClient;

    //
    // Register an exit procedure
    //
    UT_RegisterExit(putTask, CMSExitProc, pcmClient);
    pcmClient->exitProcRegistered = TRUE;

    fRegistered = TRUE;

DC_EXIT_POINT:

    UT_Unlock(UTLOCK_T120);

    DebugExitBOOL(CMS_Register, fRegistered);
    return(fRegistered);
}



//
// CMS_Deregister()                                         
//
void CMS_Deregister(PCM_CLIENT * ppcmClient)
{
    PCM_CLIENT      pcmClient = *ppcmClient;

    DebugEntry(CMS_Deregister);

    //
    // Check the parameters are valid                                      
    //
    UT_Lock(UTLOCK_T120);

    ValidateCMS(pcmClient);

    //
    // Only actually deregister the client if the registration count has   
    // reached zero.                                                       
    //
    pcmClient->useCount--;
    if (pcmClient->useCount != 0)
    {
        DC_QUIT;
    }

    //
    // Call the exit procedure to do our local cleanup                     
    //
    CMSExitProc(pcmClient);

DC_EXIT_POINT:
    *ppcmClient = NULL;

    UT_Unlock(UTLOCK_T120);

    DebugExitVOID(CMS_Deregister);
}




//
// CMS_ChannelRegister()                                    
//
BOOL CMS_ChannelRegister
(
    PCM_CLIENT      pcmClient,
    UINT            channelKey,
    UINT            channelID
)
{
    BOOL                fRegistered = FALSE;
    GCCRegistryKey      gccRegistryKey;
    GCCError            rcGCC;
    char                dcgKeyStr[sizeof(GROUPWARE_GCC_APPLICATION_KEY)+MAX_ITOA_LENGTH];

    DebugEntry(CMS_ChannelRegister);

    UT_Lock(UTLOCK_T120);

    //
    // Check the CMG task
    //
    ValidateUTClient(g_putCMG);

    //
    // Check the parameters are valid
    //
    ValidateCMP(g_pcmPrimary);
    ValidateCMS(pcmClient);

    //
    // If we are not in a call it is an error.                             
    //
    if (!g_pcmPrimary->currentCall)
    {
        WARNING_OUT(("CMS_ChannelRegister failed; not in call"));
        DC_QUIT;
    }
    if (!g_pcmPrimary->bGCCEnrolled)
    {
        WARNING_OUT(("CMS_ChannelRegister failed; not enrolled in call"));
        DC_QUIT;
    }

    // Make sure we don't have one pending already
    ASSERT(pcmClient->channelKey == 0);
   
    TRACE_OUT(("Channel ID %u Key %u", channelID, channelKey));

    //
    // Build a GCCRegistryKey based on our channelKey:                 
    //
    CMPBuildGCCRegistryKey(channelKey, &gccRegistryKey, dcgKeyStr);

    //
    // Now call through to GCC.  GCC will invoke our callback when it  
    // has processed the request.                                      
    //
    rcGCC = g_pcmPrimary->pIAppSap->RegisterChannel(
                                          g_pcmPrimary->callID,
                                          &gccRegistryKey,
                                          (ChannelID)channelID);
    if (rcGCC)
    {
        //
        // Tell the secondary client that the request failed.          
        //
        WARNING_OUT(( "Error %#lx from GCCRegisterChannel (key: %u)",
            rcGCC, channelKey));
    }
    else
    {
        // Remember so we can post confirm event back to proper task
        pcmClient->channelKey = channelKey;

        fRegistered = TRUE;
    }

DC_EXIT_POINT:
    UT_Unlock(UTLOCK_T120);

    DebugExitBOOL(CMS_ChannelRegister, fRegistered);
    return(fRegistered);
}



//
// CMS_AssignTokenId()                                      
//
BOOL CMS_AssignTokenId
(
    PCM_CLIENT  pcmClient,
    UINT        tokenKey
)
{
    GCCRegistryKey  gccRegistryKey;
    GCCError        rcGCC;
    char            dcgKeyStr[sizeof(GROUPWARE_GCC_APPLICATION_KEY)+MAX_ITOA_LENGTH];
    BOOL            fAssigned = FALSE;

    DebugEntry(CMS_AssignTokenId);

    UT_Lock(UTLOCK_T120);

    //
    // Check the parameters are valid
    //
    ValidateCMP(g_pcmPrimary);
    ValidateCMS(pcmClient);

    ValidateUTClient(g_putCMG);

    if (!g_pcmPrimary->currentCall)
    {
        WARNING_OUT(("CMS_AssignTokenId failing; not in call"));
        DC_QUIT;
    }
    if (!g_pcmPrimary->bGCCEnrolled)
    {
        WARNING_OUT(("CMS_AssignTokenId failing; not enrolled in call"));
        DC_QUIT;
    }

    // Make sure we don't have one already
    ASSERT(pcmClient->tokenKey == 0);

    //
    // Build a GCCRegistryKey based on our tokenKey:                   
    //
    CMPBuildGCCRegistryKey(tokenKey, &gccRegistryKey, dcgKeyStr);

    //
    // Now call through to GCC.  GCC will invoke our callback when it  
    // has processed the request.                                      
    //
    rcGCC = g_pcmPrimary->pIAppSap->RegistryAssignToken(
        g_pcmPrimary->callID, &gccRegistryKey);
    if (rcGCC)
    {
        //
        // Tell the secondary client that the request failed.          
        //
        WARNING_OUT(( "Error %x from GCCAssignToken (key: %u)",
            rcGCC, tokenKey));
    }
    else
    {
        // Remember so we can post confirm to proper task
        pcmClient->tokenKey = tokenKey;
        fAssigned = TRUE;
    }

DC_EXIT_POINT:
    UT_Unlock(UTLOCK_T120);

    DebugExitBOOL(CMS_AssignTokenId, fAssigned);
    return(fAssigned);
}


//
// CMSExitProc()                                    
//
void CALLBACK CMSExitProc(LPVOID data)
{
    PCM_CLIENT pcmClient = (PCM_CLIENT)data;

    DebugEntry(CMSExitProc);

    UT_Lock(UTLOCK_T120);

    //
    // Check parameters                                                    
    //
    ValidateCMS(pcmClient);

    //
    // Deregister exit procedure
    //
    if (pcmClient->exitProcRegistered)
    {
        UT_DeregisterExit(pcmClient->putTask,
                          CMSExitProc,
                          pcmClient);
        pcmClient->exitProcRegistered = FALSE;
    }

    //
    // Remove the task entry from the primary's list
    //
    g_pcmPrimary->tasks[pcmClient->taskType] = NULL;
    UT_FreeRefCount((void**)&g_pcmPrimary, TRUE);

    //
    // Free the client data
    //
    delete pcmClient;

    UT_Unlock(UTLOCK_T120);

    DebugExitVOID(CMSExitProc);
}


BOOL WINAPI CMS_GetStatus(PCM_STATUS pcmStatus)
{
    BOOL    inCall;

    DebugEntry(CMS_GetStatus);

    UT_Lock(UTLOCK_T120);

    ASSERT(!IsBadWritePtr(pcmStatus, sizeof(CM_STATUS)));
    ZeroMemory(pcmStatus, sizeof(CM_STATUS));

    ValidateCMP(g_pcmPrimary);

    T120_GetNodeName(pcmStatus->localName, CCHMAX(pcmStatus->localName));

    pcmStatus->localHandle      = g_pcmPrimary->gccUserID;
    pcmStatus->peopleCount      = g_pcmPrimary->peopleCount;
    pcmStatus->fTopProvider     = g_pcmPrimary->fTopProvider;
    pcmStatus->topProviderID    = g_pcmPrimary->gccTopProviderID;

    //
    // Fill in information about other primary                             
    //
    pcmStatus->callID    = g_pcmPrimary->callID;
    inCall = (g_pcmPrimary->currentCall != FALSE);

    UT_Unlock(UTLOCK_T120);

    DebugExitBOOL(CMS_GetStatus, inCall);
    return(inCall);
}
