Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1578 lines
44 KiB

  1. #include "precomp.h"
  2. //
  3. // CMG.C
  4. // Call Management
  5. //
  6. // Copyright(c) Microsoft 1997-
  7. //
  8. #define MLZ_FILE_ZONE ZONE_NET
  9. GUID g_csguidMeetingSettings = GUID_MTGSETTINGS;
  10. //
  11. // CMP_Init()
  12. //
  13. BOOL CMP_Init(BOOL * pfCleanup)
  14. {
  15. BOOL rc = FALSE;
  16. GCCError gcc_rc;
  17. DebugEntry(CMP_Init);
  18. UT_Lock(UTLOCK_T120);
  19. if (g_putCMG || g_pcmPrimary)
  20. {
  21. *pfCleanup = FALSE;
  22. ERROR_OUT(("Can't start CMP primary task; already running"));
  23. DC_QUIT;
  24. }
  25. else
  26. {
  27. *pfCleanup = TRUE;
  28. }
  29. //
  30. // Register CMG task
  31. //
  32. if (!UT_InitTask(UTTASK_CMG, &g_putCMG))
  33. {
  34. ERROR_OUT(("Failed to start CMG task"));
  35. DC_QUIT;
  36. }
  37. //
  38. // Allocate a Call Manager handle, ref counted
  39. //
  40. g_pcmPrimary = (PCM_PRIMARY)UT_MallocRefCount(sizeof(CM_PRIMARY), TRUE);
  41. if (!g_pcmPrimary)
  42. {
  43. ERROR_OUT(("CMP_Init failed to allocate CM_PRIMARY data"));
  44. DC_QUIT;
  45. }
  46. SET_STAMP(g_pcmPrimary, CMPRIMARY);
  47. g_pcmPrimary->putTask = g_putCMG;
  48. //
  49. // Init the people list
  50. //
  51. COM_BasedListInit(&(g_pcmPrimary->people));
  52. //
  53. // Get the local user name
  54. //
  55. COM_GetSiteName(g_pcmPrimary->localName, sizeof(g_pcmPrimary->localName));
  56. //
  57. // Register event and exit procedures
  58. //
  59. UT_RegisterExit(g_putCMG, CMPExitProc, g_pcmPrimary);
  60. g_pcmPrimary->exitProcRegistered = TRUE;
  61. //
  62. // - GCCCreateSap, which is the interesting one.
  63. //
  64. gcc_rc = GCC_CreateAppSap((IGCCAppSap **) &(g_pcmPrimary->pIAppSap),
  65. g_pcmPrimary,
  66. CMPGCCCallback);
  67. if (GCC_NO_ERROR != gcc_rc || NULL == g_pcmPrimary->pIAppSap)
  68. {
  69. ERROR_OUT(( "Error from GCCCreateSap"));
  70. DC_QUIT;
  71. }
  72. rc = TRUE;
  73. DC_EXIT_POINT:
  74. UT_Unlock(UTLOCK_T120);
  75. DebugExitBOOL(CMP_Init, rc);
  76. return(rc);
  77. }
  78. //
  79. // CMP_Term()
  80. //
  81. void CMP_Term(void)
  82. {
  83. DebugEntry(CMP_Term);
  84. UT_Lock(UTLOCK_T120);
  85. if (g_pcmPrimary)
  86. {
  87. ValidateCMP(g_pcmPrimary);
  88. ValidateUTClient(g_putCMG);
  89. //
  90. // Unregister our GCC SAP.
  91. //
  92. if (NULL != g_pcmPrimary->pIAppSap)
  93. {
  94. g_pcmPrimary->pIAppSap->ReleaseInterface();
  95. g_pcmPrimary->pIAppSap = NULL;
  96. }
  97. //
  98. // Call the exit procedure to do all our termination
  99. //
  100. CMPExitProc(g_pcmPrimary);
  101. }
  102. UT_TermTask(&g_putCMG);
  103. UT_Unlock(UTLOCK_T120);
  104. DebugExitVOID(CMP_Term);
  105. }
  106. //
  107. // CMPExitProc()
  108. //
  109. void CALLBACK CMPExitProc(LPVOID data)
  110. {
  111. PCM_PRIMARY pcmPrimary = (PCM_PRIMARY)data;
  112. DebugEntry(CMPExitProc);
  113. UT_Lock(UTLOCK_T120);
  114. //
  115. // Check parameters
  116. //
  117. ValidateCMP(pcmPrimary);
  118. ASSERT(pcmPrimary == g_pcmPrimary);
  119. //
  120. // Deregister the exit procedure.
  121. //
  122. if (pcmPrimary->exitProcRegistered)
  123. {
  124. UT_DeregisterExit(pcmPrimary->putTask,
  125. CMPExitProc,
  126. pcmPrimary);
  127. pcmPrimary->exitProcRegistered = FALSE;
  128. }
  129. CMPCallEnded(pcmPrimary);
  130. //
  131. // Free the CMP data
  132. //
  133. UT_FreeRefCount((void**)&g_pcmPrimary, TRUE);
  134. UT_Unlock(UTLOCK_T120);
  135. DebugExitVOID(CMPExitProc);
  136. }
  137. //
  138. // CMPCallEnded()
  139. //
  140. void CMPCallEnded
  141. (
  142. PCM_PRIMARY pcmPrimary
  143. )
  144. {
  145. PCM_PERSON pPerson;
  146. PCM_PERSON pPersonT;
  147. int cmTask;
  148. DebugEntry(CMPCallEnded);
  149. ValidateCMP(pcmPrimary);
  150. if (!(pcmPrimary->currentCall))
  151. {
  152. TRACE_OUT(("CMCallEnded: not in call"));
  153. DC_QUIT;
  154. }
  155. //
  156. // Issue CMS_PERSON_LEFT events for all people still in the call.
  157. // Do this back to front.
  158. //
  159. pPerson = (PCM_PERSON)COM_BasedListLast(&(pcmPrimary->people), FIELD_OFFSET(CM_PERSON, chain));
  160. while (pPerson != NULL)
  161. {
  162. ASSERT(pcmPrimary->peopleCount > 0);
  163. TRACE_OUT(("Person [%d] LEAVING call", pPerson->netID));
  164. //
  165. // Get the previous person
  166. //
  167. pPersonT = (PCM_PERSON)COM_BasedListPrev(&(pcmPrimary->people), pPerson,
  168. FIELD_OFFSET(CM_PERSON, chain));
  169. //
  170. // Remove this guy from the list
  171. //
  172. COM_BasedListRemove(&(pPerson->chain));
  173. pcmPrimary->peopleCount--;
  174. //
  175. // Notify people of his leaving
  176. //
  177. CMPBroadcast(pcmPrimary,
  178. CMS_PERSON_LEFT,
  179. pcmPrimary->peopleCount,
  180. pPerson->netID);
  181. //
  182. // Free the memory for the item
  183. //
  184. delete pPerson;
  185. //
  186. // Move the previous person in the list
  187. pPerson = pPersonT;
  188. }
  189. //
  190. // Inform all registered secondary tasks of call ending (call
  191. // CMbroadcast() with CMS_END_CALL)
  192. //
  193. CMPBroadcast(pcmPrimary,
  194. CMS_END_CALL,
  195. 0,
  196. pcmPrimary->callID);
  197. //
  198. // Reset the current call vars
  199. //
  200. pcmPrimary->currentCall = FALSE;
  201. pcmPrimary->fTopProvider = FALSE;
  202. pcmPrimary->callID = 0;
  203. pcmPrimary->gccUserID = 0;
  204. pcmPrimary->gccTopProviderID = 0;
  205. //
  206. // Discard outstanding channel/token requests
  207. //
  208. for (cmTask = CMTASK_FIRST; cmTask < CMTASK_MAX; cmTask++)
  209. {
  210. if (pcmPrimary->tasks[cmTask])
  211. {
  212. pcmPrimary->tasks[cmTask]->channelKey = 0;
  213. pcmPrimary->tasks[cmTask]->tokenKey = 0;
  214. }
  215. }
  216. DC_EXIT_POINT:
  217. //
  218. // Nobody should be in the call anymore
  219. //
  220. ASSERT(pcmPrimary->peopleCount == 0);
  221. DebugExitVOID(CMCallEnded);
  222. }
  223. //
  224. // CMPGCCCallback
  225. //
  226. void CALLBACK CMPGCCCallback(GCCAppSapMsg * gccMessage)
  227. {
  228. PCM_PRIMARY pcmPrimary;
  229. GCCConferenceID confID;
  230. GCCApplicationRoster FAR * FAR * pRosterList;
  231. UINT roster;
  232. LPOSTR pOctetString;
  233. GCCObjectKey FAR * pObjectKey;
  234. UINT checkLen;
  235. DebugEntry(CMPGCCCallback);
  236. UT_Lock(UTLOCK_T120);
  237. //
  238. // The userDefined parameter is the Primary's PCM_CLIENT.
  239. //
  240. pcmPrimary = (PCM_PRIMARY)gccMessage->pAppData;
  241. if (pcmPrimary != g_pcmPrimary)
  242. {
  243. ASSERT(NULL == g_pcmPrimary);
  244. return;
  245. }
  246. ValidateCMP(pcmPrimary);
  247. switch (gccMessage->eMsgType)
  248. {
  249. case GCC_PERMIT_TO_ENROLL_INDICATION:
  250. {
  251. //
  252. // This indicates a conference has started:
  253. //
  254. CMPProcessPermitToEnroll(pcmPrimary,
  255. &gccMessage->AppPermissionToEnrollInd);
  256. }
  257. break;
  258. case GCC_ENROLL_CONFIRM:
  259. {
  260. //
  261. // This contains the result of a GCCApplicationEnrollRequest.
  262. //
  263. CMPProcessEnrollConfirm(pcmPrimary,
  264. &gccMessage->AppEnrollConfirm);
  265. }
  266. break;
  267. case GCC_REGISTER_CHANNEL_CONFIRM:
  268. {
  269. //
  270. // This contains the result of a GCCRegisterChannelRequest.
  271. //
  272. CMPProcessRegistryConfirm(
  273. pcmPrimary,
  274. gccMessage->eMsgType,
  275. &gccMessage->RegistryConfirm);
  276. }
  277. break;
  278. case GCC_ASSIGN_TOKEN_CONFIRM:
  279. {
  280. //
  281. // This contains the result of a GCCRegistryAssignTokenRequest.
  282. //
  283. CMPProcessRegistryConfirm(
  284. pcmPrimary,
  285. gccMessage->eMsgType,
  286. &gccMessage->RegistryConfirm);
  287. }
  288. break;
  289. case GCC_APP_ROSTER_REPORT_INDICATION:
  290. {
  291. //
  292. // This indicates that the application roster has changed.
  293. //
  294. confID = gccMessage->AppRosterReportInd.nConfID;
  295. pRosterList = gccMessage->AppRosterReportInd.apAppRosters;
  296. for (roster = 0;
  297. roster < gccMessage->AppRosterReportInd.cRosters;
  298. roster++)
  299. {
  300. //
  301. // Check this app roster to see if it relates to the
  302. // Groupware session (the first check is because we always
  303. // use a NON_STANDARD application key).
  304. //
  305. pObjectKey = &(pRosterList[roster]->
  306. session_key.application_protocol_key);
  307. //
  308. // We only ever use a non standard key.
  309. //
  310. if (pObjectKey->key_type != GCC_H221_NONSTANDARD_KEY)
  311. {
  312. TRACE_OUT(("Standard key, so not a roster we are interested in..."));
  313. continue;
  314. }
  315. pOctetString = &pObjectKey->h221_non_standard_id;
  316. //
  317. // Now check the octet string. It should be the same
  318. // length as our hardcoded GROUPWARE- string (including
  319. // NULL term) and should match byte for byte:
  320. //
  321. checkLen = sizeof(GROUPWARE_GCC_APPLICATION_KEY);
  322. if ((pOctetString->length != checkLen)
  323. ||
  324. (memcmp(pOctetString->value,
  325. GROUPWARE_GCC_APPLICATION_KEY,
  326. checkLen) != 0))
  327. {
  328. //
  329. // This roster is not for our session - go to the next
  330. // one.
  331. //
  332. TRACE_OUT(("Roster not for Groupware session - ignore"));
  333. continue;
  334. }
  335. //
  336. // Process the application roster.
  337. //
  338. CMPProcessAppRoster(pcmPrimary,
  339. confID,
  340. pRosterList[roster]);
  341. }
  342. }
  343. break;
  344. }
  345. UT_Unlock(UTLOCK_T120);
  346. DebugExitVOID(CMPGCCCallback);
  347. }
  348. //
  349. //
  350. // CMPBuildGCCRegistryKey(...)
  351. //
  352. //
  353. void CMPBuildGCCRegistryKey
  354. (
  355. UINT dcgKeyNum,
  356. GCCRegistryKey FAR * pGCCKey,
  357. LPSTR dcgKeyStr
  358. )
  359. {
  360. DebugEntry(CMPBuildGCCRegistryKey);
  361. //
  362. // Build up a string of the form "Groupware-XX" where XX is a string
  363. // representation (in decimal) of the <dcgKey> parameter passed in.
  364. //
  365. memcpy(dcgKeyStr, GROUPWARE_GCC_APPLICATION_KEY, sizeof(GROUPWARE_GCC_APPLICATION_KEY)-1);
  366. wsprintf(dcgKeyStr+sizeof(GROUPWARE_GCC_APPLICATION_KEY)-1, "%d",
  367. dcgKeyNum);
  368. //
  369. // Now build the GCCRegistryKey. This involves putting a pointer to
  370. // our static <dcgKeyStr> deep inside the GCC structure. We also store
  371. // the length, which is lstrlen+1, because we want to include the
  372. // NULLTERM explicitly (since GCC treats the octet_string as an
  373. // arbitrary array of bytes).
  374. //
  375. pGCCKey->session_key.application_protocol_key.
  376. key_type = GCC_H221_NONSTANDARD_KEY;
  377. pGCCKey->session_key.application_protocol_key.h221_non_standard_id.
  378. length = sizeof(GROUPWARE_GCC_APPLICATION_KEY);
  379. pGCCKey->session_key.application_protocol_key.h221_non_standard_id.
  380. value = (LPBYTE) GROUPWARE_GCC_APPLICATION_KEY;
  381. pGCCKey->session_key.session_id = 0;
  382. pGCCKey->resource_id.length =
  383. (sizeof(GROUPWARE_GCC_APPLICATION_KEY) +
  384. lstrlen(&dcgKeyStr[sizeof(GROUPWARE_GCC_APPLICATION_KEY)-1]));
  385. pGCCKey->resource_id.value = (LPBYTE) dcgKeyStr;
  386. DebugExitVOID(CMPBuildGCCRegistryKey);
  387. }
  388. //
  389. // CMPProcessPermitToEnroll(...)
  390. //
  391. void CMPProcessPermitToEnroll
  392. (
  393. PCM_PRIMARY pcmPrimary,
  394. GCCAppPermissionToEnrollInd * pMsg
  395. )
  396. {
  397. DebugEntry(CMPProcessPermitToEnroll);
  398. ValidateCMP(pcmPrimary);
  399. //
  400. // We will send CMS_PERSON_JOINED events when we receive a
  401. // GCC_APP_ROSTER_REPORT_INDICATION.
  402. //
  403. if (pMsg->fPermissionGranted)
  404. {
  405. // CALL STARTED
  406. //
  407. // If we haven't had a NCS yet then we store the conference ID.
  408. // Otherwise ignore it.
  409. //
  410. ASSERT(!pcmPrimary->currentCall);
  411. //
  412. // Initially, we do not consider ourselves to be in the call - we will
  413. // add an entry when we get the ENROLL_CONFIRM:
  414. //
  415. ASSERT(pcmPrimary->peopleCount == 0);
  416. pcmPrimary->currentCall = TRUE;
  417. pcmPrimary->callID = pMsg->nConfID;
  418. pcmPrimary->fTopProvider =
  419. pcmPrimary->pIAppSap->IsThisNodeTopProvider(pMsg->nConfID);
  420. //
  421. // Our person data:
  422. //
  423. COM_GetSiteName(pcmPrimary->localName, sizeof(pcmPrimary->localName));
  424. //
  425. // Tell GCC whether we're interested:
  426. //
  427. if (!CMPGCCEnroll(pcmPrimary, pMsg->nConfID, TRUE))
  428. {
  429. //
  430. // We are only interested in an error if it is a Groupware conf.
  431. // All we can really do is pretend the conference has ended due
  432. // to a network error.
  433. //
  434. WARNING_OUT(("Error from CMPGCCEnroll"));
  435. CMPCallEnded(pcmPrimary);
  436. }
  437. //
  438. // The reply will arrive on a GCC_ENROLL_CONFIRM event.
  439. //
  440. }
  441. else
  442. {
  443. // CALL ENDED
  444. if (g_pcmPrimary->currentCall)
  445. {
  446. //
  447. // Inform Primary task and all secondary tasks that the call has ended
  448. //
  449. CMPCallEnded(g_pcmPrimary);
  450. //
  451. // Un-enroll from the GCC Application Roster.
  452. //
  453. if (g_pcmPrimary->bGCCEnrolled)
  454. {
  455. CMPGCCEnroll(g_pcmPrimary, g_pcmPrimary->callID, FALSE);
  456. g_pcmPrimary->bGCCEnrolled = FALSE;
  457. }
  458. }
  459. }
  460. DebugExitVOID(CMPProcessPermitToEnroll);
  461. }
  462. //
  463. //
  464. // CMPProcessEnrollConfirm(...)
  465. //
  466. //
  467. void CMPProcessEnrollConfirm
  468. (
  469. PCM_PRIMARY pcmPrimary,
  470. GCCAppEnrollConfirm * pMsg
  471. )
  472. {
  473. DebugEntry(CMPProcessEnrollConfirm);
  474. ValidateCMP(pcmPrimary);
  475. ASSERT(pcmPrimary->currentCall);
  476. ASSERT(pMsg->nConfID == pcmPrimary->callID);
  477. //
  478. // This event contains the GCC node ID (i.e. the MCS user ID of the
  479. // GCC node controller at this node). Store it for later reference
  480. // against the roster report:
  481. //
  482. TRACE_OUT(( "GCC user_id: %u", pMsg->nidMyself));
  483. pcmPrimary->gccUserID = pMsg->nidMyself;
  484. pcmPrimary->gccTopProviderID = pcmPrimary->pIAppSap->GetTopProvider(pcmPrimary->callID);
  485. ASSERT(pcmPrimary->gccTopProviderID);
  486. if (pMsg->nResult != GCC_RESULT_SUCCESSFUL)
  487. {
  488. WARNING_OUT(( "Attempt to enroll failed (reason: %u", pMsg->nResult));
  489. //
  490. // All we can really do is pretend the conference has ended due to
  491. // a network error.
  492. //
  493. CMPCallEnded(pcmPrimary);
  494. }
  495. DebugExitVOID(CMProcessEnrollConfirm);
  496. }
  497. //
  498. // CMPProcessRegistryConfirm(...)
  499. //
  500. void CMPProcessRegistryConfirm
  501. (
  502. PCM_PRIMARY pcmPrimary,
  503. GCCMessageType messageType,
  504. GCCRegistryConfirm *pConfirm
  505. )
  506. {
  507. UINT event = 0;
  508. BOOL succeeded;
  509. LPSTR pGCCKeyStr; // extracted from the GCC registry key
  510. UINT dcgKeyNum; // the value originally passed in as key
  511. UINT itemID; // can be channel or token ID
  512. int cmTask;
  513. PUT_CLIENT secondaryHandle = NULL;
  514. DebugEntry(CMPProcessRegistryConfirm);
  515. ValidateCMP(pcmPrimary);
  516. //
  517. // Check this is for the Groupware conference:
  518. //
  519. if (!pcmPrimary->currentCall ||
  520. (pConfirm->nConfID != pcmPrimary->callID))
  521. {
  522. WARNING_OUT(( "Got REGISTRY_XXX_CONFIRM for unknown conference %lu",
  523. pConfirm->nConfID));
  524. DC_QUIT;
  525. }
  526. //
  527. // Embedded deep down inside the message from GCC is a pointer to an
  528. // octet string which is of the form "Groupware-XX", where XX is a
  529. // string representation of the numeric key the original Call Manager
  530. // secondary used when registering the item. Extract it now:
  531. //
  532. pGCCKeyStr = (LPSTR)pConfirm->pRegKey->resource_id.value;
  533. dcgKeyNum = DecimalStringToUINT(&pGCCKeyStr[sizeof(GROUPWARE_GCC_APPLICATION_KEY)-1]);
  534. if (dcgKeyNum == 0)
  535. {
  536. WARNING_OUT(( "Received ASSIGN/REGISTER_CONFIRM with unknown key: %s",
  537. pGCCKeyStr));
  538. DC_QUIT;
  539. }
  540. TRACE_OUT(( "Conf ID %u, DCG Key %u, result %u",
  541. pConfirm->nConfID, dcgKeyNum, pConfirm->nResult));
  542. //
  543. // This is either a REGISTER_CHANNEL_CONFIRM or a ASSIGN_TOKEN_CONFIRM.
  544. // Check, and set up the relevant pointers:
  545. //
  546. switch (messageType)
  547. {
  548. case GCC_REGISTER_CHANNEL_CONFIRM:
  549. {
  550. event = CMS_CHANNEL_REGISTER_CONFIRM;
  551. itemID = pConfirm->pRegItem->channel_id;
  552. // Look for task that registered this channel
  553. for (cmTask = CMTASK_FIRST; cmTask < CMTASK_MAX; cmTask++)
  554. {
  555. if (pcmPrimary->tasks[cmTask] &&
  556. (pcmPrimary->tasks[cmTask]->channelKey == dcgKeyNum))
  557. {
  558. pcmPrimary->tasks[cmTask]->channelKey = 0;
  559. secondaryHandle = pcmPrimary->tasks[cmTask]->putTask;
  560. }
  561. }
  562. }
  563. break;
  564. case GCC_ASSIGN_TOKEN_CONFIRM:
  565. {
  566. event = CMS_TOKEN_ASSIGN_CONFIRM;
  567. itemID = pConfirm->pRegItem->token_id;
  568. // Look for task that assigned this token
  569. for (cmTask = CMTASK_FIRST; cmTask < CMTASK_MAX; cmTask++)
  570. {
  571. if (pcmPrimary->tasks[cmTask] &&
  572. (pcmPrimary->tasks[cmTask]->tokenKey == dcgKeyNum))
  573. {
  574. pcmPrimary->tasks[cmTask]->tokenKey = 0;
  575. secondaryHandle = pcmPrimary->tasks[cmTask]->putTask;
  576. }
  577. }
  578. }
  579. break;
  580. default:
  581. {
  582. ERROR_OUT(( "Unexpected registry event %u", messageType));
  583. DC_QUIT;
  584. }
  585. }
  586. switch (pConfirm->nResult)
  587. {
  588. case GCC_RESULT_SUCCESSFUL:
  589. {
  590. //
  591. // We were the first to register an item against this key.
  592. //
  593. TRACE_OUT(("We were first to register using key %u (itemID: %u)",
  594. dcgKeyNum, itemID));
  595. succeeded = TRUE;
  596. }
  597. break;
  598. case GCC_RESULT_ENTRY_ALREADY_EXISTS:
  599. {
  600. //
  601. // Someone beat us to it: they have registered a channel
  602. // against the key we specified. This value is in the GCC
  603. // message:
  604. //
  605. TRACE_OUT(("Another node registered using key %u (itemID: %u)",
  606. dcgKeyNum, itemID));
  607. succeeded = TRUE;
  608. }
  609. break;
  610. default:
  611. {
  612. ERROR_OUT(("Error %#hx registering/assigning item against key %u",
  613. pConfirm->nResult, dcgKeyNum));
  614. succeeded = FALSE;
  615. }
  616. break;
  617. }
  618. //
  619. // Tell the secondary about the result.
  620. //
  621. if (secondaryHandle)
  622. {
  623. UT_PostEvent(pcmPrimary->putTask,
  624. secondaryHandle,
  625. 0,
  626. event,
  627. succeeded,
  628. MAKELONG(itemID, dcgKeyNum));
  629. }
  630. DC_EXIT_POINT:
  631. DebugExitVOID(CMProcessRegistryConfirm);
  632. }
  633. //
  634. // CMPProcessAppRoster(...)
  635. //
  636. void CMPProcessAppRoster
  637. (
  638. PCM_PRIMARY pcmPrimary,
  639. GCCConferenceID confID,
  640. GCCApplicationRoster* pAppRoster
  641. )
  642. {
  643. UINT newList;
  644. UserID oldNode;
  645. UserID newNode;
  646. PCM_PERSON pPerson;
  647. PCM_PERSON pPersonT;
  648. BOOL found;
  649. int task;
  650. BOOL notInOldRoster = TRUE;
  651. BOOL inNewRoster = FALSE;
  652. DebugEntry(CMPProcessAppRoster);
  653. ValidateCMP(pcmPrimary);
  654. //
  655. // If we are not in a call ignore this.
  656. //
  657. if (!pcmPrimary->currentCall ||
  658. (confID != pcmPrimary->callID))
  659. {
  660. WARNING_OUT(("Report not for active Groupware conference - ignore"));
  661. DC_QUIT;
  662. }
  663. //
  664. // At this point, pAppRoster points to the bit of the roster which
  665. // relates to Groupware. Trace out some info:
  666. //
  667. TRACE_OUT(( "Number of records %u;", pAppRoster->number_of_records));
  668. TRACE_OUT(( "Nodes added: %s, removed: %s",
  669. (pAppRoster->nodes_were_added ? "YES" : "NO"),
  670. (pAppRoster->nodes_were_removed ? "YES" : "NO")));
  671. //
  672. // We store the GCC user IDs in shared memory as TSHR_PERSONIDs.
  673. // Compare this list of people we know to be in the call, and
  674. // * Remove people no longer around
  675. // * See if we are new to the roster
  676. // * Add people who are new
  677. //
  678. pPerson = (PCM_PERSON)COM_BasedListFirst(&(pcmPrimary->people), FIELD_OFFSET(CM_PERSON, chain));
  679. while (pPerson != NULL)
  680. {
  681. ASSERT(pcmPrimary->peopleCount > 0);
  682. oldNode = (UserID)pPerson->netID;
  683. //
  684. // Get the next guy in the list in case we remove this one.
  685. //
  686. pPersonT = (PCM_PERSON)COM_BasedListNext(&(pcmPrimary->people), pPerson,
  687. FIELD_OFFSET(CM_PERSON, chain));
  688. //
  689. // Check to see if our node is currently in the roster
  690. //
  691. if (oldNode == pcmPrimary->gccUserID)
  692. {
  693. TRACE_OUT(( "We are currently in the app roster"));
  694. notInOldRoster = FALSE;
  695. }
  696. //
  697. // ...check if they're in the new list...
  698. //
  699. found = FALSE;
  700. for (newList = 0; newList < pAppRoster->number_of_records; newList++)
  701. {
  702. if (oldNode == pAppRoster->application_record_list[newList]->node_id)
  703. {
  704. found = TRUE;
  705. break;
  706. }
  707. }
  708. if (!found)
  709. {
  710. //
  711. // This node is no longer present, so remove him.
  712. //
  713. TRACE_OUT(("Person %u left", oldNode));
  714. COM_BasedListRemove(&(pPerson->chain));
  715. pcmPrimary->peopleCount--;
  716. CMPBroadcast(pcmPrimary,
  717. CMS_PERSON_LEFT,
  718. pcmPrimary->peopleCount,
  719. oldNode);
  720. //
  721. // Free the memory for the person item
  722. //
  723. delete pPerson;
  724. }
  725. pPerson = pPersonT;
  726. }
  727. //
  728. // Now see if we are new to the roster
  729. //
  730. for (newList = 0; newList < pAppRoster->number_of_records; newList++)
  731. {
  732. if (pAppRoster->application_record_list[newList]->node_id ==
  733. pcmPrimary->gccUserID)
  734. {
  735. TRACE_OUT(( "We are in the new app roster"));
  736. inNewRoster = TRUE;
  737. break;
  738. }
  739. }
  740. if (notInOldRoster && inNewRoster)
  741. {
  742. //
  743. // We are new to the roster so we can now do all the processing we
  744. // were previously doing in the enroll confirm handler. GCC spec
  745. // requires that we do not do this until we get the roster
  746. // notification back.
  747. //
  748. // Flag we are enrolled and start registering channels etc.
  749. //
  750. pcmPrimary->bGCCEnrolled = TRUE;
  751. //
  752. // Post a CMS_NEW_CALL events to all secondary tasks
  753. //
  754. TRACE_OUT(( "Broadcasting CMS_NEW_CALL with call handle 0x%08lx",
  755. pcmPrimary->callID));
  756. //
  757. // If we are not the caller then delay the broadcast a little
  758. //
  759. CMPBroadcast(pcmPrimary, CMS_NEW_CALL,
  760. pcmPrimary->fTopProvider, pcmPrimary->callID);
  761. #ifdef _DEBUG
  762. //
  763. // Process any outstanding channel register and assign token
  764. // requests.
  765. //
  766. for (task = CMTASK_FIRST; task < CMTASK_MAX; task++)
  767. {
  768. if (pcmPrimary->tasks[task] != NULL)
  769. {
  770. ASSERT(pcmPrimary->tasks[task]->channelKey == 0);
  771. ASSERT(pcmPrimary->tasks[task]->tokenKey == 0);
  772. }
  773. }
  774. #endif // _DEBUG
  775. }
  776. //
  777. // If we are not yet enrolled in the conference then do not start
  778. // sending PERSON_JOINED notifications.
  779. //
  780. if (!pcmPrimary->bGCCEnrolled)
  781. {
  782. DC_QUIT;
  783. }
  784. //
  785. // Add the new people (this will include us). At this point, we know
  786. // that everyone in the people list is currently in the roster, since
  787. // we would have removed 'em above.
  788. //
  789. // we need to walk the existing list over and over.
  790. // But at least we can skip the people we add. So we save the current
  791. // front of the list.
  792. //
  793. pPersonT = (PCM_PERSON)COM_BasedListFirst(&(pcmPrimary->people), FIELD_OFFSET(CM_PERSON, chain));
  794. for (newList = 0; newList < pAppRoster->number_of_records; newList++)
  795. {
  796. newNode = pAppRoster->application_record_list[newList]->node_id;
  797. found = FALSE;
  798. pPerson = pPersonT;
  799. while (pPerson != NULL)
  800. {
  801. if (newNode == pPerson->netID)
  802. {
  803. //
  804. // This person already existed - don't need to do anything
  805. //
  806. found = TRUE;
  807. break; // out of inner for loop
  808. }
  809. pPerson = (PCM_PERSON)COM_BasedListNext(&(pcmPrimary->people), pPerson,
  810. FIELD_OFFSET(CM_PERSON, chain));
  811. }
  812. if (!found)
  813. {
  814. //
  815. // This dude is new; add him to our people list.
  816. //
  817. TRACE_OUT(("Person with GCC user_id %u joined", newNode));
  818. pPerson = new CM_PERSON;
  819. if (!pPerson)
  820. {
  821. //
  822. // Uh oh; can't add him.
  823. //
  824. ERROR_OUT(("Can't add person GCC user_id %u; out of memory",
  825. newNode));
  826. break;
  827. }
  828. ZeroMemory(pPerson, sizeof(*pPerson));
  829. pPerson->netID = newNode;
  830. //
  831. // LONCHANC: We should collapse all these events into a single one
  832. // that summarize all added and removed nodes,
  833. // instead of posting the events one by one.
  834. //
  835. //
  836. // Stick him in at the beginning. At least that way we don't
  837. // have to look at his record anymore.
  838. //
  839. COM_BasedListInsertAfter(&(pcmPrimary->people), &pPerson->chain);
  840. pcmPrimary->peopleCount++;
  841. CMPBroadcast(pcmPrimary,
  842. CMS_PERSON_JOINED,
  843. pcmPrimary->peopleCount,
  844. newNode);
  845. }
  846. }
  847. TRACE_OUT(( "Num people now in call %u", pcmPrimary->peopleCount));
  848. DC_EXIT_POINT:
  849. DebugExitVOID(CMPProcessAppRoster);
  850. }
  851. //
  852. // CMPBroadcast()
  853. //
  854. void CMPBroadcast
  855. (
  856. PCM_PRIMARY pcmPrimary,
  857. UINT event,
  858. UINT param1,
  859. UINT_PTR param2
  860. )
  861. {
  862. int task;
  863. DebugEntry(CMPBroadcast);
  864. ValidateCMP(pcmPrimary);
  865. //
  866. // for every secondary task
  867. //
  868. for (task = CMTASK_FIRST; task < CMTASK_MAX; task++)
  869. {
  870. if (pcmPrimary->tasks[task] != NULL)
  871. {
  872. UT_PostEvent(pcmPrimary->putTask,
  873. pcmPrimary->tasks[task]->putTask,
  874. NO_DELAY,
  875. event,
  876. param1,
  877. param2);
  878. }
  879. }
  880. DebugExitVOID(CMPBroadcast);
  881. }
  882. //
  883. // CMPGCCEnroll(...)
  884. //
  885. BOOL CMPGCCEnroll
  886. (
  887. PCM_PRIMARY pcmPrimary,
  888. GCCConferenceID conferenceID,
  889. BOOL fEnroll
  890. )
  891. {
  892. GCCError rcGCC = GCC_NO_ERROR;
  893. GCCSessionKey gccSessionKey;
  894. GCCObjectKey FAR * pGCCObjectKey;
  895. BOOL succeeded = TRUE;
  896. GCCNonCollapsingCapability caps;
  897. GCCNonCollapsingCapability* pCaps;
  898. OSTR octetString;
  899. GCCEnrollRequest er;
  900. GCCRequestTag nReqTag;
  901. DebugEntry(CMPGCCEnroll);
  902. ValidateCMP(pcmPrimary);
  903. //
  904. // Do some error checking.
  905. //
  906. if (fEnroll && pcmPrimary->bGCCEnrolled)
  907. {
  908. WARNING_OUT(("Already enrolled"));
  909. DC_QUIT;
  910. }
  911. TRACE_OUT(("CMGCCEnroll for CM_hnd 0x%08x, confID 0x%08x, in/out %d",
  912. pcmPrimary, conferenceID, fEnroll));
  913. //
  914. // Set up the session key which identifies us uniquely in the GCC
  915. // AppRoster. We use a non-standard key (because we're not part of the
  916. // T.120 standards series)
  917. //
  918. // Octet strings aren't null terminated, but we want ours to include
  919. // the NULL at the end of the C string, so specify lstrlen+1 for the
  920. // length.
  921. //
  922. pGCCObjectKey = &(gccSessionKey.application_protocol_key);
  923. pGCCObjectKey->key_type = GCC_H221_NONSTANDARD_KEY;
  924. pGCCObjectKey->h221_non_standard_id.value =
  925. (LPBYTE) GROUPWARE_GCC_APPLICATION_KEY;
  926. pGCCObjectKey->h221_non_standard_id.length =
  927. sizeof(GROUPWARE_GCC_APPLICATION_KEY);
  928. gccSessionKey.session_id = 0;
  929. //
  930. // Try to enroll/unenroll with GCC. This may fail because we have not
  931. // yet received a GCC_PERMIT_TO_ENROLL_INDICATION.
  932. //
  933. TRACE_OUT(("Enrolling local site '%s'", pcmPrimary->localName));
  934. //
  935. // Create the non-collapsing capabilites list to pass onto GCC.
  936. //
  937. octetString.length = lstrlen(pcmPrimary->localName) + 1;
  938. octetString.value = (LPBYTE) pcmPrimary->localName;
  939. caps.application_data = &octetString;
  940. caps.capability_id.capability_id_type = GCC_STANDARD_CAPABILITY;
  941. caps.capability_id.standard_capability = 0;
  942. pCaps = &caps;
  943. //
  944. // Fill in the enroll request structure
  945. //
  946. ZeroMemory(&er, sizeof(er));
  947. er.pSessionKey = &gccSessionKey;
  948. // er.fEnrollActively = FALSE;
  949. // er.nUserID = 0; // no user ID
  950. // er.fConductingCapable = FALSE;
  951. er.nStartupChannelType = MCS_STATIC_CHANNEL;
  952. er.cNonCollapsedCaps = 1;
  953. er.apNonCollapsedCaps = &pCaps;
  954. // er.cCollapsedCaps = 0;
  955. // er.apCollapsedCaps = NULL;
  956. er.fEnroll = fEnroll;
  957. rcGCC = pcmPrimary->pIAppSap->AppEnroll(
  958. conferenceID,
  959. &er,
  960. &nReqTag);
  961. if (GCC_NO_ERROR != rcGCC)
  962. {
  963. //
  964. // Leave the decision about any error processing to the caller.
  965. //
  966. TRACE_OUT(("Error 0x%08x from GCCApplicationEnrollRequest conf ID %lu enroll=%s",
  967. rcGCC, conferenceID, fEnroll ? "YES": "NO"));
  968. succeeded = FALSE;
  969. }
  970. else
  971. {
  972. //
  973. // Whether we have asked to enroll or un-enroll, we act as if we
  974. // are no longer enrolled at once. We are only really enrolled
  975. // when we receive an enroll confirm event.
  976. //
  977. pcmPrimary->bGCCEnrolled = FALSE;
  978. ASSERT(succeeded);
  979. TRACE_OUT(( "%s with conference %d", fEnroll ?
  980. "Enroll Outstanding" : "Unenrolled",
  981. conferenceID));
  982. }
  983. DC_EXIT_POINT:
  984. DebugExitBOOL(CMPGCCEnroll, succeeded);
  985. return(succeeded);
  986. }
  987. //
  988. // CMS_Register()
  989. //
  990. BOOL CMS_Register
  991. (
  992. PUT_CLIENT putTask,
  993. CMTASK taskType,
  994. PCM_CLIENT* ppcmClient
  995. )
  996. {
  997. BOOL fRegistered = FALSE;
  998. PCM_CLIENT pcmClient = NULL;
  999. DebugEntry(CMS_Register);
  1000. UT_Lock(UTLOCK_T120);
  1001. if (!g_pcmPrimary)
  1002. {
  1003. ERROR_OUT(("CMS_Register failed; primary doesn't exist"));
  1004. DC_QUIT;
  1005. }
  1006. ValidateUTClient(putTask);
  1007. ASSERT(taskType >= CMTASK_FIRST);
  1008. ASSERT(taskType < CMTASK_MAX);
  1009. *ppcmClient = NULL;
  1010. //
  1011. // Is this task already present? If so, share it
  1012. //
  1013. if (g_pcmPrimary->tasks[taskType] != NULL)
  1014. {
  1015. TRACE_OUT(("Sharing CMS task 0x%08x", g_pcmPrimary->tasks[taskType]));
  1016. *ppcmClient = g_pcmPrimary->tasks[taskType];
  1017. ValidateCMS(*ppcmClient);
  1018. (*ppcmClient)->useCount++;
  1019. // Return -- we exist.
  1020. fRegistered = TRUE;
  1021. DC_QUIT;
  1022. }
  1023. //
  1024. // If we got here the task is not a Call Manager Secondary yet, so go
  1025. // ahead with the registration.
  1026. //
  1027. //
  1028. // Allocate memory for the client
  1029. //
  1030. pcmClient = new CM_CLIENT;
  1031. if (! pcmClient)
  1032. {
  1033. ERROR_OUT(("Could not allocate CM handle"));
  1034. DC_QUIT;
  1035. }
  1036. ZeroMemory(pcmClient, sizeof(*pcmClient));
  1037. *ppcmClient = pcmClient;
  1038. //
  1039. // Fill in information
  1040. //
  1041. SET_STAMP(pcmClient, CMCLIENT);
  1042. pcmClient->putTask = putTask;
  1043. pcmClient->taskType = taskType;
  1044. pcmClient->useCount = 1;
  1045. UT_BumpUpRefCount(g_pcmPrimary);
  1046. g_pcmPrimary->tasks[taskType] = pcmClient;
  1047. //
  1048. // Register an exit procedure
  1049. //
  1050. UT_RegisterExit(putTask, CMSExitProc, pcmClient);
  1051. pcmClient->exitProcRegistered = TRUE;
  1052. fRegistered = TRUE;
  1053. DC_EXIT_POINT:
  1054. UT_Unlock(UTLOCK_T120);
  1055. DebugExitBOOL(CMS_Register, fRegistered);
  1056. return(fRegistered);
  1057. }
  1058. //
  1059. // CMS_Deregister()
  1060. //
  1061. void CMS_Deregister(PCM_CLIENT * ppcmClient)
  1062. {
  1063. PCM_CLIENT pcmClient = *ppcmClient;
  1064. DebugEntry(CMS_Deregister);
  1065. //
  1066. // Check the parameters are valid
  1067. //
  1068. UT_Lock(UTLOCK_T120);
  1069. ValidateCMS(pcmClient);
  1070. //
  1071. // Only actually deregister the client if the registration count has
  1072. // reached zero.
  1073. //
  1074. pcmClient->useCount--;
  1075. if (pcmClient->useCount != 0)
  1076. {
  1077. DC_QUIT;
  1078. }
  1079. //
  1080. // Call the exit procedure to do our local cleanup
  1081. //
  1082. CMSExitProc(pcmClient);
  1083. DC_EXIT_POINT:
  1084. *ppcmClient = NULL;
  1085. UT_Unlock(UTLOCK_T120);
  1086. DebugExitVOID(CMS_Deregister);
  1087. }
  1088. //
  1089. // CMS_GetStatus()
  1090. //
  1091. extern "C"
  1092. {
  1093. BOOL WINAPI CMS_GetStatus(PCM_STATUS pcmStatus)
  1094. {
  1095. BOOL inCall;
  1096. DebugEntry(CMS_GetStatus);
  1097. UT_Lock(UTLOCK_T120);
  1098. ASSERT(!IsBadWritePtr(pcmStatus, sizeof(CM_STATUS)));
  1099. ZeroMemory(pcmStatus, sizeof(CM_STATUS));
  1100. ValidateCMP(g_pcmPrimary);
  1101. //
  1102. // Copy the statistics from the control block
  1103. //
  1104. lstrcpy(pcmStatus->localName, g_pcmPrimary->localName);
  1105. pcmStatus->localHandle = g_pcmPrimary->gccUserID;
  1106. pcmStatus->peopleCount = g_pcmPrimary->peopleCount;
  1107. pcmStatus->fTopProvider = g_pcmPrimary->fTopProvider;
  1108. pcmStatus->topProviderID = g_pcmPrimary->gccTopProviderID;
  1109. //
  1110. // Meeting settings
  1111. //
  1112. pcmStatus->attendeePermissions = NM_PERMIT_ALL;
  1113. if (!pcmStatus->fTopProvider)
  1114. {
  1115. T120_GetUserData(g_pcmPrimary->callID, g_pcmPrimary->gccTopProviderID,
  1116. &g_csguidMeetingSettings, (LPBYTE)&pcmStatus->attendeePermissions,
  1117. sizeof(pcmStatus->attendeePermissions));
  1118. }
  1119. //
  1120. // Fill in information about other primary
  1121. //
  1122. pcmStatus->callID = g_pcmPrimary->callID;
  1123. inCall = (g_pcmPrimary->currentCall != FALSE);
  1124. UT_Unlock(UTLOCK_T120);
  1125. DebugExitBOOL(CMS_GetStatus, inCall);
  1126. return(inCall);
  1127. }
  1128. }
  1129. //
  1130. // CMS_ChannelRegister()
  1131. //
  1132. BOOL CMS_ChannelRegister
  1133. (
  1134. PCM_CLIENT pcmClient,
  1135. UINT channelKey,
  1136. UINT channelID
  1137. )
  1138. {
  1139. BOOL fRegistered = FALSE;
  1140. GCCRegistryKey gccRegistryKey;
  1141. GCCError rcGCC;
  1142. char dcgKeyStr[sizeof(GROUPWARE_GCC_APPLICATION_KEY)+MAX_ITOA_LENGTH];
  1143. DebugEntry(CMS_ChannelRegister);
  1144. UT_Lock(UTLOCK_T120);
  1145. //
  1146. // Check the CMG task
  1147. //
  1148. ValidateUTClient(g_putCMG);
  1149. //
  1150. // Check the parameters are valid
  1151. //
  1152. ValidateCMP(g_pcmPrimary);
  1153. ValidateCMS(pcmClient);
  1154. //
  1155. // If we are not in a call it is an error.
  1156. //
  1157. if (!g_pcmPrimary->currentCall)
  1158. {
  1159. WARNING_OUT(("CMS_ChannelRegister failed; not in call"));
  1160. DC_QUIT;
  1161. }
  1162. if (!g_pcmPrimary->bGCCEnrolled)
  1163. {
  1164. WARNING_OUT(("CMS_ChannelRegister failed; not enrolled in call"));
  1165. DC_QUIT;
  1166. }
  1167. // Make sure we don't have one pending already
  1168. ASSERT(pcmClient->channelKey == 0);
  1169. TRACE_OUT(("Channel ID %u Key %u", channelID, channelKey));
  1170. //
  1171. // Build a GCCRegistryKey based on our channelKey:
  1172. //
  1173. CMPBuildGCCRegistryKey(channelKey, &gccRegistryKey, dcgKeyStr);
  1174. //
  1175. // Now call through to GCC. GCC will invoke our callback when it
  1176. // has processed the request.
  1177. //
  1178. rcGCC = g_pcmPrimary->pIAppSap->RegisterChannel(
  1179. g_pcmPrimary->callID,
  1180. &gccRegistryKey,
  1181. (ChannelID)channelID);
  1182. if (rcGCC)
  1183. {
  1184. //
  1185. // Tell the secondary client that the request failed.
  1186. //
  1187. WARNING_OUT(( "Error %#lx from GCCRegisterChannel (key: %u)",
  1188. rcGCC, channelKey));
  1189. }
  1190. else
  1191. {
  1192. // Remember so we can post confirm event back to proper task
  1193. pcmClient->channelKey = channelKey;
  1194. fRegistered = TRUE;
  1195. }
  1196. DC_EXIT_POINT:
  1197. UT_Unlock(UTLOCK_T120);
  1198. DebugExitBOOL(CMS_ChannelRegister, fRegistered);
  1199. return(fRegistered);
  1200. }
  1201. //
  1202. // CMS_AssignTokenId()
  1203. //
  1204. BOOL CMS_AssignTokenId
  1205. (
  1206. PCM_CLIENT pcmClient,
  1207. UINT tokenKey
  1208. )
  1209. {
  1210. GCCRegistryKey gccRegistryKey;
  1211. GCCError rcGCC;
  1212. char dcgKeyStr[sizeof(GROUPWARE_GCC_APPLICATION_KEY)+MAX_ITOA_LENGTH];
  1213. BOOL fAssigned = FALSE;
  1214. DebugEntry(CMS_AssignTokenId);
  1215. UT_Lock(UTLOCK_T120);
  1216. //
  1217. // Check the parameters are valid
  1218. //
  1219. ValidateCMP(g_pcmPrimary);
  1220. ValidateCMS(pcmClient);
  1221. ValidateUTClient(g_putCMG);
  1222. if (!g_pcmPrimary->currentCall)
  1223. {
  1224. WARNING_OUT(("CMS_AssignTokenId failing; not in call"));
  1225. DC_QUIT;
  1226. }
  1227. if (!g_pcmPrimary->bGCCEnrolled)
  1228. {
  1229. WARNING_OUT(("CMS_AssignTokenId failing; not enrolled in call"));
  1230. DC_QUIT;
  1231. }
  1232. // Make sure we don't have one already
  1233. ASSERT(pcmClient->tokenKey == 0);
  1234. //
  1235. // Build a GCCRegistryKey based on our tokenKey:
  1236. //
  1237. CMPBuildGCCRegistryKey(tokenKey, &gccRegistryKey, dcgKeyStr);
  1238. //
  1239. // Now call through to GCC. GCC will invoke our callback when it
  1240. // has processed the request.
  1241. //
  1242. rcGCC = g_pcmPrimary->pIAppSap->RegistryAssignToken(
  1243. g_pcmPrimary->callID, &gccRegistryKey);
  1244. if (rcGCC)
  1245. {
  1246. //
  1247. // Tell the secondary client that the request failed.
  1248. //
  1249. WARNING_OUT(( "Error %x from GCCAssignToken (key: %u)",
  1250. rcGCC, tokenKey));
  1251. }
  1252. else
  1253. {
  1254. // Remember so we can post confirm to proper task
  1255. pcmClient->tokenKey = tokenKey;
  1256. fAssigned = TRUE;
  1257. }
  1258. DC_EXIT_POINT:
  1259. UT_Unlock(UTLOCK_T120);
  1260. DebugExitBOOL(CMS_AssignTokenId, fAssigned);
  1261. return(fAssigned);
  1262. }
  1263. //
  1264. // CMSExitProc()
  1265. //
  1266. void CALLBACK CMSExitProc(LPVOID data)
  1267. {
  1268. PCM_CLIENT pcmClient = (PCM_CLIENT)data;
  1269. DebugEntry(CMSExitProc);
  1270. UT_Lock(UTLOCK_T120);
  1271. //
  1272. // Check parameters
  1273. //
  1274. ValidateCMS(pcmClient);
  1275. //
  1276. // Deregister exit procedure
  1277. //
  1278. if (pcmClient->exitProcRegistered)
  1279. {
  1280. UT_DeregisterExit(pcmClient->putTask,
  1281. CMSExitProc,
  1282. pcmClient);
  1283. pcmClient->exitProcRegistered = FALSE;
  1284. }
  1285. //
  1286. // Remove the task entry from the primary's list
  1287. //
  1288. g_pcmPrimary->tasks[pcmClient->taskType] = NULL;
  1289. UT_FreeRefCount((void**)&g_pcmPrimary, TRUE);
  1290. //
  1291. // Free the client data
  1292. //
  1293. delete pcmClient;
  1294. UT_Unlock(UTLOCK_T120);
  1295. DebugExitVOID(CMSExitProc);
  1296. }