#include "precomp.h" // // SC.CPP // Share Controller // // NOTE: // We must take the UTLOCK_AS every time we // * create/destroy the share object // * add/remove a person from the share // // Copyright(c) Microsoft 1997- // #define MLZ_FILE_ZONE ZONE_CORE // // SC_Init() // Initializes the share controller // BOOL SC_Init(void) { BOOL rc = FALSE; DebugEntry(SC_Init); ASSERT(!g_asSession.callID); ASSERT(!g_asSession.gccID); ASSERT(g_asSession.scState == SCS_TERM); // // Register as a Call Manager Secondary task // if (!CMS_Register(g_putAS, CMTASK_DCS, &g_pcmClientSc)) { ERROR_OUT(( "Failed to register with CMS")); DC_QUIT; } g_asSession.scState = SCS_INIT; TRACE_OUT(("g_asSession.scState is SCS_INIT")); rc = TRUE; DC_EXIT_POINT: DebugExitBOOL(SC_Init, rc); return(rc); } // // SC_Term() // // See sc.h for description. // // void SC_Term(void) { DebugEntry(SC_Term); // // Clear up the core's state by generating appropriate PARTY_DELETED and // SHARE_ENDED events. // switch (g_asSession.scState) { case SCS_SHARING: case SCS_SHAREENDING: case SCS_SHAREPENDING: SC_End(); break; } // // Deregister from the Call Manager // if (g_pcmClientSc) { CMS_Deregister(&g_pcmClientSc); } g_asSession.gccID = 0; g_asSession.callID = 0; g_asSession.scState = SCS_TERM; TRACE_OUT(("g_asSession.scState is SCS_TERM")); DebugExitVOID(SC_Term); } // // SC_CreateShare() // Creates a new share or joins an existing one // BOOL SC_CreateShare(UINT s20CreateOrJoin) { BOOL rc = FALSE; DebugEntry(SC_CreateShare); // // If we are initialised but there is no Call Manager call, return the // race condition. // if ((g_asSession.scState != SCS_INIT) && (g_asSession.scState != SCS_SHAREPENDING)) { TRACE_OUT(("Ignoring SC_CreateShare() request; in bad state %d", g_asSession.scState)); DC_QUIT; } if (!g_asSession.callID) { WARNING_OUT(("Ignoring SC_CreateShare() request; not in T120 call")); DC_QUIT; } // // Remember that we created this share. // g_asSession.fShareCreator = (s20CreateOrJoin == S20_CREATE); TRACE_OUT(("CreatedShare is %s", (s20CreateOrJoin == S20_CREATE) ? "TRUE" : "FALSE")); g_asSession.scState = SCS_SHAREPENDING; TRACE_OUT(("g_asSession.scState is SCS_SHAREPENDING")); rc = S20CreateOrJoinShare(s20CreateOrJoin, g_asSession.callID); if (!rc) { WARNING_OUT(("%s failed", (s20CreateOrJoin == S20_CREATE ? "S20_CREATE" : "S20_JOIN"))); } DC_EXIT_POINT: DebugExitBOOL(SC_CreateShare, rc); return(rc); } // // SC_EndShare() // This will end a share if we are in one or in the middle of establishing // one, and clean up afterwards. // void SC_EndShare(void) { DebugEntry(SC_EndShare); if (g_asSession.scState <= SCS_SHAREENDING) { TRACE_OUT(("Ignoring SC_EndShare(); nothing to do in state %d", g_asSession.scState)); } else { // // If we are in a share or in the middle of creating/joining one, stop // the process. // // // Kill the share // NOTE that this will call SC_End(), when we come back // from this function our g_asSession.scState() should be SCS_INIT. // g_asSession.scState = SCS_SHAREENDING; TRACE_OUT(("g_asSession.scState is SCS_SHAREENDING")); S20LeaveOrEndShare(); g_asSession.scState = SCS_INIT; } DebugExitVOID(SC_EndShare); } // // SC_PersonFromNetID() // ASPerson * ASShare::SC_PersonFromNetID(MCSID mcsID) { ASPerson * pasPerson; DebugEntry(SC_PersonFromNetID); ASSERT(mcsID != MCSID_NULL); // // Search for the mcsID. // if (!SC_ValidateNetID(mcsID, &pasPerson)) { ERROR_OUT(("Invalid [%u]", mcsID)); } DebugExitPVOID(ASShare::SC_PersonFromNetID, pasPerson); return(pasPerson); } // // SC_ValidateNetID() // BOOL ASShare::SC_ValidateNetID ( MCSID mcsID, ASPerson * * ppasPerson ) { BOOL rc = FALSE; ASPerson * pasPerson; DebugEntry(ASShare::SC_ValidateNetID); // Init to empty pasPerson = NULL; // // MCSID_NULL matches no one. // if (mcsID == MCSID_NULL) { WARNING_OUT(("SC_ValidateNetID called with MCSID_NULL")); DC_QUIT; } // // Search for the mcsID. // for (pasPerson = m_pasLocal; pasPerson != NULL; pasPerson = pasPerson->pasNext) { ValidatePerson(pasPerson); if (pasPerson->mcsID == mcsID) { // // Found required person, set return values and quit // rc = TRUE; break; } } DC_EXIT_POINT: if (ppasPerson) { *ppasPerson = pasPerson; } DebugExitBOOL(ASShare::SC_ValidateNetID, rc); return(rc); } // // SC_PersonFromGccID() // ASPerson * ASShare::SC_PersonFromGccID(UINT gccID) { ASPerson * pasPerson; DebugEntry(ASShare::SC_PersonFromGccID); for (pasPerson = m_pasLocal; pasPerson != NULL; pasPerson = pasPerson->pasNext) { ValidatePerson(pasPerson); if (pasPerson->cpcCaps.share.gccID == gccID) { // Found it break; } } DebugExitPVOID(ASShare::SC_PersonFromGccID, pasPerson); return(pasPerson); } // // SC_Start() // Inits the share (if all is OK), and adds local person to it. // BOOL SC_Start(UINT mcsID) { BOOL rc = FALSE; ASShare * pShare; ASPerson * pasPerson; DebugEntry(SC_Start); ASSERT(g_asSession.callID); ASSERT(g_asSession.gccID); if ((g_asSession.scState != SCS_INIT) && (g_asSession.scState != SCS_SHAREPENDING)) { WARNING_OUT(("Ignoring SC_Start(); in bad state")); DC_QUIT; } if (g_asSession.pShare) { WARNING_OUT(("Ignoring SC_Start(); have ASShare object already")); DC_QUIT; } g_asSession.scState = SCS_SHARING; TRACE_OUT(("g_asSession.scState is SCS_SHARING")); #ifdef _DEBUG // // Use this to calculate time between joining share and getting // first view // g_asSession.scShareTime = ::GetTickCount(); #endif // _DEBUG // // Allocate the share object and add the local dude to the share. // pShare = new ASShare; if (pShare) { ZeroMemory(pShare, sizeof(*(pShare))); SET_STAMP(pShare, SHARE); rc = pShare->SC_ShareStarting(); } UT_Lock(UTLOCK_AS); g_asSession.pShare = pShare; UT_Unlock(UTLOCK_AS); if (!rc) { ERROR_OUT(("Can't create/init ASShare")); DC_QUIT; } DCS_NotifyUI(SH_EVT_SHARE_STARTED, 0, 0); // // Join local dude into share. If that fails, also bail out. // pasPerson = g_asSession.pShare->SC_PartyJoiningShare(mcsID, g_asSession.achLocalName, sizeof(g_cpcLocalCaps), &g_cpcLocalCaps); if (!pasPerson) { ERROR_OUT(("Local person not joined into share successfully")); DC_QUIT; } // // Okay! We have a share, and it's set up. // // // Tell the UI that we're in the share. // DCS_NotifyUI(SH_EVT_PERSON_JOINED, pasPerson->cpcCaps.share.gccID, 0); // // Start periodic processing if ViewSelf or record/playback is on. // if (g_asSession.pShare->m_scfViewSelf) { SCH_ContinueScheduling(SCH_MODE_NORMAL); } rc = TRUE; DC_EXIT_POINT: DebugExitBOOL(SC_Start, rc); return(rc); } // // SC_End() // Removes any remotes from the share, removes the local person, and // cleans up after the fact. // void SC_End(void) { DebugEntry(SC_End); if (g_asSession.scState < SCS_SHAREENDING) { TRACE_OUT(("Ignoring SC_EVENT_SHAREENDED")); } else { if (g_asSession.pShare) { g_asSession.pShare->SC_ShareEnded(); UT_Lock(UTLOCK_AS); delete g_asSession.pShare; g_asSession.pShare = NULL; UT_Unlock(UTLOCK_AS); DCS_NotifyUI(SH_EVT_SHARE_ENDED, 0, 0); } // // If the previous state was SCS_SHARE_PENDING then we // may have ended up here as the result of a 'back off' after // trying to create two shares. Let's try to join again... // if (g_asSession.fShareCreator) { g_asSession.fShareCreator = FALSE; TRACE_OUT(("CreatedShare is FALSE")); if (g_asSession.scState == SCS_SHAREPENDING) { WARNING_OUT(("Got share end while share pending - retry join")); UT_PostEvent(g_putAS, g_putAS, 0, CMS_NEW_CALL, 0, 0); } } g_asSession.scState = SCS_INIT; TRACE_OUT(("g_asSession.scState is SCS_INIT")); } g_s20ShareCorrelator = 0; // // Reset the queued control packets. // g_s20ControlPacketQHead = 0; g_s20ControlPacketQTail = 0; g_s20State = S20_NO_SHARE; TRACE_OUT(("g_s20State is S20_NO_SHARE")); DebugExitVOID(SC_End); } // // SC_PartyAdded() // BOOL ASShare::SC_PartyAdded ( UINT mcsID, LPSTR szName, UINT cbCaps, LPVOID pCaps ) { BOOL rc = FALSE; ASPerson * pasPerson; if (g_asSession.scState != SCS_SHARING) { WARNING_OUT(("Ignoring SC_EVENT_PARTYADDED; not in share")); DC_QUIT; } ASSERT(g_asSession.callID); ASSERT(g_asSession.gccID); // // A remote party is joining the share // // // Notify everybody // pasPerson = SC_PartyJoiningShare(mcsID, szName, cbCaps, pCaps); if (!pasPerson) { WARNING_OUT(("SC_PartyJoiningShare failed for remote [%d]", mcsID)); DC_QUIT; } // // SYNC now // We should NEVER SEND ANY PACKETS when syncing. We aren't ready // because we haven't joined the person into the share. yet. // So we simply set variables for us to send data off the next // periodic schedule. // #ifdef _DEBUG m_scfInSync = TRUE; #endif // _DEBUG // // Stuff needed for being in the share, hosting or not // DCS_SyncOutgoing(); OE_SyncOutgoing(); if (m_pHost != NULL) { // // Common to both starting sharing and retransmitting sharing info // m_pHost->HET_SyncCommon(); m_pHost->HET_SyncAlreadyHosting(); m_pHost->CA_SyncAlreadyHosting(); } #ifdef _DEBUG m_scfInSync = FALSE; #endif // _DEBUG // // DO THIS LAST -- tell the UI this person is in the share. // DCS_NotifyUI(SH_EVT_PERSON_JOINED, pasPerson->cpcCaps.share.gccID, 0); // // Start periodic processing // SCH_ContinueScheduling(SCH_MODE_NORMAL); rc = TRUE; DC_EXIT_POINT: DebugExitBOOL(ASShare::SC_PartyAdded, rc); return(rc); } // // SC_PartyDeleted() // void ASShare::SC_PartyDeleted(UINT mcsID) { if ((g_asSession.scState != SCS_SHARING) && (g_asSession.scState != SCS_SHAREENDING)) { WARNING_OUT(("Ignoring SC_EVENT_PARTYDELETED; wrong state")); DC_QUIT; } SC_PartyLeftShare(mcsID); DC_EXIT_POINT: DebugExitVOID(ASShare::SC_PartyDeleted); } // // SC_ReceivedPacket() // void ASShare::SC_ReceivedPacket(PS20DATAPACKET pPacket) { ASPerson * pasPerson; PSNIPACKET pSNIPacket; DebugEntry(ASShare::SC_ReceivedPacket); if (g_asSession.scState != SCS_SHARING) { WARNING_OUT(("Ignoring received data because we're not in right state")); DC_QUIT; } // // Ignore packets on streams we don't know about. // if ((pPacket->stream < SC_STREAM_LOW) || (pPacket->stream > SC_STREAM_HIGH)) { TRACE_OUT(("Ignoring received data on unrecognized stream %d", pPacket->stream)); DC_QUIT; } // // It is possible to get a packet from a person we do not know // about. // // This can happen if we join a conference that has an existing // share session. All existing parties will be sending data on // the channels we have joined but we will not yet have // received the events to add them to our share session. // // Data packets from unknown people are ignored (ignoring this // data is OK as we will resync with them when they are added // to our share session) // if (!SC_ValidateNetID(pPacket->header.user, &pasPerson)) { WARNING_OUT(("Ignoring data packet from unknown person [%d]", pPacket->header.user)); DC_QUIT; } if (pPacket->data.dataType == DT_SNI) { // // This is an SNI packet - handle it here. // pSNIPacket = (PSNIPACKET)pPacket; switch(pSNIPacket->message) { case SNI_MSG_SYNC: // // This is a sync message. // if (pSNIPacket->destination == m_pasLocal->mcsID) { // // This sync message is for us. // pasPerson->scSyncRecStatus[pPacket->stream-1] = SC_SYNCED; } else { TRACE_OUT(("Ignoring SYNC on stream %d for [%d] from [%d]", pPacket->stream, pSNIPacket->destination, pPacket->header.user)); } break; default: ERROR_OUT(("Unknown SNI message %u", pSNIPacket->message)); break; } } else if (pasPerson->scSyncRecStatus[pPacket->stream-1] == SC_SYNCED) { PS20DATAPACKET pPacketUse; UINT cbBufferSize; UINT compression; BOOL decompressed; UINT dictionary; // // Decompress the packet if necessary // // // Use the temporary buffer. This will never fail, so we don't // need to check the return value. THIS MEANS THAT THE HANDLING OF // INCOMING PACKETS CAN NEVER IMMEDIATELY TURN AROUND AND SEND AN // OUTGOING PACKET. Our scratch buffer is in use. // pPacketUse = (PS20DATAPACKET)m_ascTmpBuffer; TRACE_OUT(( "Got data pkt type %u from [%d], compression %u", pPacket->data.dataType, pasPerson->mcsID, pPacket->data.compressionType)); // // If the packet has CT_OLD_COMPRESSED set, it has used simple PKZIP // compression // if (pPacket->data.compressionType & CT_OLD_COMPRESSED) { compression = CT_PKZIP; } else { compression = pPacket->data.compressionType; } TRACE_OUT(( "packet compressed with algorithm %u", compression)); // // If the packet is compressed, decompress it now // if (compression) { PGDC_DICTIONARY pgdcDict = NULL; // // Copy the uncompressed packet header into the buffer. // memcpy(pPacketUse, pPacket, sizeof(*pPacket)); cbBufferSize = TSHR_MAX_SEND_PKT - sizeof(*pPacket); if (compression == CT_PERSIST_PKZIP) { // // Figure out what dictionary to use based on stream priority // switch (pPacket->stream) { case PROT_STR_UPDATES: dictionary = GDC_DICT_UPDATES; break; case PROT_STR_MISC: dictionary = GDC_DICT_MISC; break; case PROT_STR_INPUT: dictionary = GDC_DICT_INPUT; break; default: ERROR_OUT(("Unrecognized stream ID")); break; } pgdcDict = pasPerson->adcsDict + dictionary; } else if (compression != CT_PKZIP) { // // If this isn't PKZIP or PERSIST_PKZIP, we don't know what the // heck it is (corrupted packet or incompatible T.128. Bail // out now. // WARNING_OUT(("SC_ReceivedPacket: ignoring packet, don't recognize compression type")); DC_QUIT; } // // Decompress the data following the packet header. // This should never fail - if it does, the data has probably been // corrupted. // decompressed = GDC_Decompress(pgdcDict, m_agdcWorkBuf, (LPBYTE)(pPacket + 1), pPacket->data.compressedLength - sizeof(DATAPACKETHEADER), (LPBYTE)(pPacketUse + 1), &cbBufferSize); if (!decompressed) { ERROR_OUT(( "Failed to decompress packet from [%d]!", pasPerson->mcsID)); DC_QUIT; } } else { // We have received an uncompressed buffer. Since we may modify the // buffer's contents and what we have is an MCS buffer that may be // sent to other nodes, we copy the data. memcpy(pPacketUse, pPacket, pPacket->dataLength + sizeof(S20DATAPACKET) - sizeof(DATAPACKETHEADER)); } // The packet (pPacketUse) is now decompressed // // ROUTE the packet // TRACE_OUT(("SC_ReceivedPacket: Received packet type %04d size %04d from [%d]", pPacketUse->data.dataType, pPacketUse->data.compressedLength, pPacketUse->header.user)); switch (pPacketUse->data.dataType) { case DT_CA: CA_ReceivedPacket(pasPerson, pPacketUse); break; case DT_CA30: CA30_ReceivedPacket(pasPerson, pPacketUse); break; case DT_IM: IM_ReceivedPacket(pasPerson, pPacketUse); break; case DT_HET: case DT_HET30: HET_ReceivedPacket(pasPerson, pPacketUse); break; case DT_UP: UP_ReceivedPacket(pasPerson, pPacketUse); break; case DT_FH: FH_ReceivedPacket(pasPerson, pPacketUse); break; case DT_CM: CM_ReceivedPacket(pasPerson, pPacketUse); break; case DT_CPC: CPC_ReceivedPacket(pasPerson, pPacketUse); break; case DT_AWC: AWC_ReceivedPacket(pasPerson, pPacketUse); break; case DT_UNUSED_DS: TRACE_OUT(("Ignoring DS packet received from NM 2.x node")); break; case DT_UNUSED_USR_FH_11: // Old R.11 FH packets case DT_UNUSED_USR_FH_10: // Old R.10 FH packets case DT_UNUSED_HCA: // Old High-Level Control Arbiter case DT_UNUSED_SC: // Old R.11 SC packets default: ERROR_OUT(( "Invalid packet received %u", pPacketUse->data.dataType)); break; } } DC_EXIT_POINT: DebugExitVOID(ASShare::SC_ReceivedPacket); } // // SC_ShareStarting() // Share initialization // // This in turn calls other component share starting // BOOL ASShare::SC_ShareStarting(void) { BOOL rc = FALSE; BOOL fViewSelf; DebugEntry(ASShare::SC_ShareStarting); // // SC specific init // // Find out whether to view own shared stuff (a handy debugging tool) COM_ReadProfInt(DBG_INI_SECTION_NAME, VIEW_INI_VIEWSELF, FALSE, &fViewSelf); m_scfViewSelf = (fViewSelf != FALSE); // Create scratch compression buffer for outgoing/incoming packets m_ascTmpBuffer = new BYTE[TSHR_MAX_SEND_PKT]; if (!m_ascTmpBuffer) { ERROR_OUT(("SC_Init: couldn't allocate m_ascTmpBuffer")); DC_QUIT; } // Share version m_scShareVersion = CAPS_VERSION_CURRENT; // // Component inits // if (!BCD_ShareStarting()) { ERROR_OUT(("BCD_ShareStarting failed")); DC_QUIT; } if (!IM_ShareStarting()) { ERROR_OUT(("IM_ShareStarting failed")); DC_QUIT; } if (!CM_ShareStarting()) { ERROR_OUT(("CM_ShareStarting failed")); DC_QUIT; } if (!USR_ShareStarting()) { ERROR_OUT(("USR_ShareStarting failed")); DC_QUIT; } if (!VIEW_ShareStarting()) { ERROR_OUT(("VIEW_ShareStarting failed")); DC_QUIT; } rc = TRUE; DC_EXIT_POINT: DebugExitBOOL(ASShare::SC_ShareStarting, rc); return(rc); } // // SC_ShareEnded() // Share cleanup // // This in turn calls other component share ended routines // void ASShare::SC_ShareEnded(void) { DebugEntry(ASShare::SC_ShareEnded); // // Delete remotes from share // if (m_pasLocal) { while (m_pasLocal->pasNext) { SC_PartyDeleted(m_pasLocal->pasNext->mcsID); } // // Delete local self from share // SC_PartyDeleted(m_pasLocal->mcsID); } // // Component Notifications // VIEW_ShareEnded(); USR_ShareEnded(); CM_ShareEnded(); IM_ShareEnded(); BCD_ShareEnded(); // // SC specific term // // Free scratch buffer if (m_ascTmpBuffer) { delete[] m_ascTmpBuffer; m_ascTmpBuffer = NULL; } DebugExitVOID(ASShare::SC_ShareEnded); } // // SC_PartyJoiningShare() // // Called when a new party is joining the share. This is an internal // function because it is the SC which calls all these functions. The // processing done here relies on the capabilities - so it is in here as // this is called after CPC_PartyJoiningShare. // // RETURNS: // // TRUE if the party can join the share. // // FALSE if the party can NOT join the share. // // ASPerson * ASShare::SC_PartyJoiningShare ( UINT mcsID, LPSTR szName, UINT cbCaps, LPVOID pCaps ) { BOOL rc = FALSE; ASPerson * pasPerson = NULL; DebugEntry(ASShare::SC_PartyJoiningShare); // // Allocate an ASPerson for this dude. If this is the local person, // it gets stuck at the front. Otherwise it gets stuck just past // the front. // pasPerson = SC_PersonAllocate(mcsID, szName); if (!pasPerson) { ERROR_OUT(("SC_PartyAdded: can't allocate ASPerson for [%d]", mcsID)); DC_QUIT; } // // DO THIS FIRST -- we need the person's caps set up // if (!CPC_PartyJoiningShare(pasPerson, cbCaps, pCaps)) { ERROR_OUT(("CPC_PartyJoiningShare failed for [%d]", pasPerson->mcsID)); DC_QUIT; } // // Call the component joined routines. // if (!DCS_PartyJoiningShare(pasPerson)) { ERROR_OUT(("DCS_PartyJoiningShare failed for [%d]", pasPerson->mcsID)); DC_QUIT; } if (!CM_PartyJoiningShare(pasPerson)) { ERROR_OUT(("CM_PartyJoiningShare failed for [%d]", pasPerson->mcsID)); DC_QUIT; } if (!HET_PartyJoiningShare(pasPerson)) { ERROR_OUT(("HET_PartyJoiningShare failed for [%d]", pasPerson->mcsID)); DC_QUIT; } // // NOW THE PERSON IS JOINED. // Recalculate capabilities of the share with this new person. // SC_RecalcCaps(TRUE); rc = TRUE; DC_EXIT_POINT: if (!rc) { // // Don't worry, this person object will still be cleaned up, // code at a higher layer will free by using the MCSID. // pasPerson = NULL; } DebugExitPVOID(ASShare::SC_PartyJoiningShare, pasPerson); return(pasPerson); } // // SC_RecalcCaps() // // Recalculates share capabilities after somebody has joined or left the // share. // void ASShare::SC_RecalcCaps(BOOL fJoiner) { ASPerson * pasT; DebugEntry(ASShare::SC_RecalcCaps); // // DO THIS FIRST -- recalculate the share version. // ValidatePerson(m_pasLocal); m_scShareVersion = m_pasLocal->cpcCaps.general.version; for (pasT = m_pasLocal->pasNext; pasT != NULL; pasT = pasT->pasNext) { ValidatePerson(pasT); m_scShareVersion = min(m_scShareVersion, pasT->cpcCaps.general.version); } // // Do viewing & hosting stuff first // DCS_RecalcCaps(fJoiner); OE_RecalcCaps(fJoiner); // // Do hosting stuff second // USR_RecalcCaps(fJoiner); CM_RecalcCaps(fJoiner); PM_RecalcCaps(fJoiner); SBC_RecalcCaps(fJoiner); SSI_RecalcCaps(fJoiner); DebugExitVOID(ASShare::SC_RecalcCaps); } // // FUNCTION: SC_PartyLeftShare() // // DESCRIPTION: // // Called when a party has left the share. // // void ASShare::SC_PartyLeftShare(UINT mcsID) { ASPerson * pasPerson; ASPerson * pasT; DebugEntry(SC_PartyLeftShare); if (!SC_ValidateNetID(mcsID, &pasPerson)) { TRACE_OUT(("Couldn't find ASPerson for [%d]", mcsID)); DC_QUIT; } // Tell the UI this dude is gone if (!pasPerson->cpcCaps.share.gccID) { WARNING_OUT(("Skipping PartyLeftShare for person [%d], no GCC id", pasPerson->mcsID)); DC_QUIT; } DCS_NotifyUI(SH_EVT_PERSON_LEFT, pasPerson->cpcCaps.share.gccID, 0); // // Tell everybody this person is gone. // // // Notes on order of PartyLeftShare calls // // 1. HET must be called first, since that halts any sharing from this // person, before we kick the person out of the share. // // 2. CA must be called before IM (as CA calls IM functions) // // // This will stop hosting early HET_PartyLeftShare(pasPerson); CA_PartyLeftShare(pasPerson); CM_PartyLeftShare(pasPerson); VIEW_PartyLeftShare(pasPerson); PM_PartyLeftShare(pasPerson); RBC_PartyLeftShare(pasPerson); OD2_PartyLeftShare(pasPerson); OE_PartyLeftShare(pasPerson); DCS_PartyLeftShare(pasPerson); // // Free the person // SC_PersonFree(pasPerson); // // Recalculate the caps with him gone. But there's no point in doing // this if it's the local dude, since the share will exit imminently. // if (m_pasLocal) { SC_RecalcCaps(FALSE); } DC_EXIT_POINT: DebugExitVOID(ASShare::SC_PartyLeftShare); } // // FUNCTION: SCCheckForCMCall // // DESCRIPTION: // // This is called when we want to check if a CM call now exists (and do // whatever is appropriate to join it etc). // // PARAMETERS: NONE // // RETURNS: TRUE if success; otherwise, FALSE. // // void SCCheckForCMCall(void) { CM_STATUS cmStatus; DebugEntry(SCCheckForCMCall); ASSERT(g_asSession.scState == SCS_INIT); // // See if a call already exists // if (!g_asSession.callID) { if (CMS_GetStatus(&cmStatus)) { // // The AS lock protects the g_asSession fields. // TRACE_OUT(("AS LOCK: SCCheckForCMCall")); UT_Lock(UTLOCK_AS); g_asSession.callID = cmStatus.callID; // // This is the time to update our local person name. It's // on our thread, but before the control packets exchange it // lstrcpy(g_asSession.achLocalName, cmStatus.localName); g_asSession.cchLocalName = lstrlen(g_asSession.achLocalName); TRACE_OUT(("Local Name is %s", g_asSession.achLocalName)); g_asSession.gccID = cmStatus.localHandle; UT_Unlock(UTLOCK_AS); TRACE_OUT(("AS UNLOCK: SCCheckForCMCall")); } } if (g_asSession.callID) { SC_CreateShare(S20_JOIN); } DebugExitVOID(SCCheckForCMCall); } #ifdef _DEBUG void ASShare::ValidatePerson(ASPerson * pasPerson) { ASSERT(!IsBadWritePtr(pasPerson, sizeof(ASPerson))); ASSERT(!lstrcmp(pasPerson->stamp.idStamp, "ASPerso")); ASSERT(pasPerson->mcsID != MCSID_NULL); } void ASShare::ValidateView(ASPerson * pasPerson) { ValidatePerson(pasPerson); ASSERT(!IsBadWritePtr(pasPerson->m_pView, sizeof(ASView))); ASSERT(!lstrcmp(pasPerson->m_pView->stamp.idStamp, "ASVIEW")); } #endif // _DEBUG // // SC_PersonAllocate() // This allocates a new ASPerson structure, fills in the debug/mcsID fields, // and links it into the people-in-the-conference list. // // Eventually, all the PartyJoiningShare routines that simply init a field // should go away and that info put here. // ASPerson * ASShare::SC_PersonAllocate(UINT mcsID, LPSTR szName) { ASPerson * pasNew; DebugEntry(ASShare::SC_PersonAllocate); pasNew = new ASPerson; if (!pasNew) { ERROR_OUT(("Unable to allocate a new ASPerson")); DC_QUIT; } ZeroMemory(pasNew, sizeof(*pasNew)); SET_STAMP(pasNew, Person); // // Set up mcsID and name // pasNew->mcsID = mcsID; lstrcpyn(pasNew->scName, szName, TSHR_MAX_PERSON_NAME_LEN); UT_Lock(UTLOCK_AS); // // Is this the local person? // if (!m_pasLocal) { m_pasLocal = pasNew; } else { UINT streamID; // // This is a remote. Set up the sync status right away in case // the join process fails in the middle. Cleaning up will undo // this always. // // // Mark this person's streams as needing a sync from us before we // can send data to him // And and that we need a sync from him on each stream before we'll // receive data from him // for (streamID = SC_STREAM_LOW; streamID <= SC_STREAM_HIGH; streamID++ ) { // // Set up the sync status. // ASSERT(pasNew->scSyncSendStatus[streamID-1] == SC_NOT_SYNCED); ASSERT(pasNew->scSyncRecStatus[streamID-1] == SC_NOT_SYNCED); m_ascSynced[streamID-1]++; } // // Link into list // pasNew->pasNext = m_pasLocal->pasNext; m_pasLocal->pasNext = pasNew; } UT_Unlock(UTLOCK_AS); DC_EXIT_POINT: DebugExitPVOID(ASShare::SC_PersonAllocate, pasNew); return(pasNew); } // // SC_PersonFree() // This gets a person out of the linked list and frees the memory for them. // void ASShare::SC_PersonFree(ASPerson * pasFree) { ASPerson ** ppasPerson; UINT streamID; DebugEntry(ASShare::SC_PersonFree); ValidatePerson(pasFree); for (ppasPerson = &m_pasLocal; *(ppasPerson) != NULL; ppasPerson = &((*ppasPerson)->pasNext)) { if ((*ppasPerson) == pasFree) { // // Found it. // TRACE_OUT(("SC_PersonUnhook: unhooking person [%d]", pasFree->mcsID)); if (pasFree == m_pasLocal) { ASSERT(pasFree->pasNext == NULL); } else { // // Clear syncs // // If this person was never synced, subtract them from the // global "needing sync" count on each stream // for (streamID = SC_STREAM_LOW; streamID <= SC_STREAM_HIGH; streamID++ ) { if (pasFree->scSyncSendStatus[streamID-1] == SC_NOT_SYNCED) { ASSERT(m_ascSynced[streamID-1] > 0); m_ascSynced[streamID-1]--; } } } UT_Lock(UTLOCK_AS); // // Fix up linked list. // (*ppasPerson) = pasFree->pasNext; #ifdef _DEBUG ZeroMemory(pasFree, sizeof(ASPerson)); #endif // _DEBUG delete pasFree; UT_Unlock(UTLOCK_AS); DC_QUIT; } } // // We didn't find this guy in the list--this is very bad. // ERROR_OUT(("SC_PersonFree: didn't find person %d", pasFree)); DC_EXIT_POINT: DebugExitVOID(ASShare::SC_PersonFree); } // // SC_AllocPkt() // Allocates a SEND packet // PS20DATAPACKET ASShare::SC_AllocPkt ( UINT streamID, UINT nodeID, UINT cbSizePkt ) { PS20DATAPACKET pPacket = NULL; DebugEntry(ASShare::SC_AllocPkt); if (g_asSession.scState != SCS_SHARING) { TRACE_OUT(("SC_AllocPkt failed; share is ending")); DC_QUIT; } ASSERT((streamID >= SC_STREAM_LOW) && (streamID <= SC_STREAM_HIGH)); ASSERT(cbSizePkt >= sizeof(S20DATAPACKET)); // // We'd better not be in the middle of a sync! // ASSERT(!m_scfInSync); // // Try and send any outstanding syncs // if (!SCSyncStream(streamID)) { // // If the stream is still not synced, don't allocate the packet // WARNING_OUT(("SC_AllocPkt failed; outstanding syncs are present")); DC_QUIT; } pPacket = S20_AllocDataPkt(streamID, nodeID, cbSizePkt); DC_EXIT_POINT: DebugExitPVOID(ASShare::SC_AllocPkt, pPacket); return(pPacket); } // // SCSyncStream() // // This broadcasts a SNI sync packet intended for a new person who has just // joined the share. That person ignores all received data from us until // they get the sync. That's because data in transit before they are synced // could refer to PKZIP data they don't have, second level order encoding // info they can't decode, orders they can't process, etc. // // When we receive a SYNC from a remote, we then know that following // data from that remote will make sense. The remote has settled us into // the share, and the data encorporates our capabilities and will not refer // to prior state info from before we joined the share. // // NOTE that in 2.x, this was O(N^2) where N is the number of people now in // the share! Each person in the share would send SNI sync packets for each // stream for each other person in the share, even for people who weren't new. // But those people wouldn't reset received state, and would (could) continue // processing data from us. When they finally got their sync packet, it // would do nothing! Even more worst, 2 of the 5 streams are never used, // and one stream is only used when a person is hosting. So 3 of these 5 // O(N^2) broadcasts were useless all or the majority of the time. // // So now we only send SNI sync packets for new joiners. This makes joining // an O(N) broadcast algorithm. // // LAURABU BOGUS // Post Beta1, we can make this even better. Each broadcast is itself O(N) // packets. So for beta1, joining/syncing is O(N^2) packets instead of // O(N^3) packets. With targeted sends (not broadcasts) whenever possible, // we can drop this to O(N) total packets. // // NOTE also that no real app sharing packets are sent on a stream until // we have fully synced everybody. That means we've reduced a lot the // lag between somebody dialing into a conference and their seeing a result, // and everybody else being able to work again. // BOOL ASShare::SCSyncStream(UINT streamID) { ASPerson * pasPerson; PSNIPACKET pSNIPacket; BOOL rc = TRUE; DebugEntry(ASShare::SCSyncStream); // // Loop through each person in the call broadcasting sync packets as // necessary. // // LAURABU BOGUS // We can change this to a targeted send after Beta 1. // // // Note that new people are added to the front of the this. So we will // bail out of this loop very quickly when sending syncs to newcomers. // ValidatePerson(m_pasLocal); pasPerson = m_pasLocal->pasNext; while ((m_ascSynced[streamID-1] > 0) && (pasPerson != NULL)) { ValidatePerson(pasPerson); // // If this person is new, we need to send them a SYNC packet so // they know we are done processing their join and know they // are in the share. // if (pasPerson->scSyncSendStatus[streamID-1] != SC_SYNCED) { TRACE_OUT(("Syncing stream %d for person [%d] in broadcast way", streamID, pasPerson->mcsID)); // // YES, syncs are broadcast even though they have the mcsID of // just one person, the person they are intended for. Since we // broadcast all state data, we also broadcast syncs. That's // the only way to make sure the packets arrive in the same // order, SYNC before data. // pSNIPacket = (PSNIPACKET)S20_AllocDataPkt(streamID, g_s20BroadcastID, sizeof(SNIPACKET)); if (!pSNIPacket) { TRACE_OUT(("Failed to alloc SNI sync packet")); rc = FALSE; break; } // // Set SNI packet fields // pSNIPacket->header.data.dataType = DT_SNI; pSNIPacket->message = SNI_MSG_SYNC; pSNIPacket->destination = (NET_UID)pasPerson->mcsID; S20_SendDataPkt(streamID, g_s20BroadcastID, &(pSNIPacket->header)); pasPerson->scSyncSendStatus[streamID-1] = SC_SYNCED; ASSERT(m_ascSynced[streamID-1] > 0); m_ascSynced[streamID-1]--; } pasPerson = pasPerson->pasNext; } DebugExitBOOL(ASShare::SCSyncStream, rc); return(rc); }