mirror of https://github.com/tongzx/nt5src
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.
1572 lines
37 KiB
1572 lines
37 KiB
#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);
|
|
}
|
|
|