windows-nt/Source/XPSP1/NT/termsrv/remdsk/rds/as/cpi32/sc.cpp

1573 lines
37 KiB
C++
Raw Normal View History

2020-09-26 03:20:57 -05:00
#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);
}