#include "precomp.h"


//
// S20.CPP
// T.128 Protocol
//
// Copyright(c) Microsoft 1997-
//

#define MLZ_FILE_ZONE  ZONE_NET



//
// S20_Init()
// Initializes the T.128 protocol layer
//
BOOL  S20_Init(void)
{
    BOOL    rc = FALSE;

    DebugEntry(S20_Init);

    ASSERT(g_s20State == S20_TERM);

    //
    // Register with the network layer.
    //
    if (!MG_Register(MGTASK_DCS, &g_s20pmgClient, g_putAS))
    {
        ERROR_OUT(("Failed to register MG layer"));
        DC_QUIT;
    }

    g_s20State = S20_INIT;
    TRACE_OUT(("g_s20State is S20_INIT"));

    rc = TRUE;

DC_EXIT_POINT:
    DebugExitBOOL(S20_Init, rc);
    return(rc);
}



//
// S20_Term()
// This cleans up the T.128 protocol layer.
//
void S20_Term(void)
{
    DebugEntry(S20_Term);

    //
    // Note that this case statement is unusual in that it falls through
    // from each condition.  This happens to suit the termination
    // processing rather well.
    //
    switch (g_s20State)
    {
        case S20_IN_SHARE:
        case S20_SHARE_PEND:
            //
            // Notify share ended
            //
            SC_End();

            //
            // FALL THROUGH
            //

        case S20_NO_SHARE:
        case S20_JOIN_PEND:
            //
            // Leave our channels
            //
            if (g_s20BroadcastID != 0)
            {
                MG_ChannelLeave(g_s20pmgClient, g_s20BroadcastID);
                g_s20BroadcastID = 0;
            }
            if (g_s20JoinedLocal)
            {
                MG_ChannelLeave(g_s20pmgClient, g_s20LocalID);
                g_s20JoinedLocal = FALSE;
            }

            //
            // FALL THROUGH
            //

        case S20_ATTACH_PEND:
            //
            // Detach from the domain.
            //
            MG_Detach(g_s20pmgClient);

        case S20_INIT:
            //
            // We may end up here with g_s20BroadcastID & g_s20JoinedLocal
            // non-zero if Term was called in the middle of a share.  Clear these
            // variables so when we start back up again via Init, it
            // works the same way as first initialization.
            //
            // Note we do not need to leave the channels.
            //
            g_s20BroadcastID = 0;
            g_s20JoinedLocal = FALSE;

            //
            // Deregister.
            //
            MG_Deregister(&g_s20pmgClient);
            g_s20State = S20_TERM;
            TRACE_OUT(("g_s20State is S20_TERM"));

        case S20_TERM:
            //
            // Finally we break out.
            //
            break;

        default:
            ERROR_OUT(("invalid state %d", g_s20State));
            break;
    }

    DebugExitVOID(S20_Term);
}



//
// S20_AllocPkt
// Allocates a SEND packet for either the S20 protocol, syncs, or data
//
PS20DATAPACKET S20_AllocDataPkt
(
    UINT            streamID,
    UINT            nodeID,                 // One person or broadcast
    UINT            cbSizePacket
)
{
    PS20DATAPACKET  pS20Packet = NULL;
    NET_PRIORITY    priority;
    BOOL            rc = FALSE;

    DebugEntry(S20_AllocDataPkt);

    ASSERT(g_s20State == S20_IN_SHARE);

    //
    // Try to send queued control packets first.
    //
    if (S20SendQueuedControlPackets() != 0)
    {
        //
        // If there are still queued control packets then don't allow any
        // allocation.
        //
        DC_QUIT;
    }

    priority = S20StreamToS20Priority(streamID);

    //
    // Note:
    // Sends to an individual node are NOT flow-controlled.  Only the
    // global app sharing channel is.
    //
    if (MG_GetBuffer(g_s20pmgClient, cbSizePacket, priority,
                        (NET_CHANNEL_ID)nodeID, (void **)&pS20Packet) != 0)
    {
        TRACE_OUT(("MG_GetBuffer failed; can't allocate S20 packet"));
    }
    else
    {
        pS20Packet->header.packetType   = S20_DATA | S20_ALL_VERSIONS;
        pS20Packet->header.user         = g_s20LocalID;

        pS20Packet->correlator  = g_s20ShareCorrelator;
        pS20Packet->stream      = 0;
        pS20Packet->dataLength  = cbSizePacket - sizeof(S20DATAPACKET) + sizeof(DATAPACKETHEADER);

        rc = TRUE;
    }

DC_EXIT_POINT:
    DebugExitPVOID(S20_AllocDataPkt, pS20Packet);
    return(pS20Packet);
}


//
// S20_FreeDataPkt - see s20.h
//
void  S20_FreeDataPkt(PS20DATAPACKET pS20Packet)
{
    DebugEntry(S20_FreeDataPkt);

    MG_FreeBuffer(g_s20pmgClient, (void **)&pS20Packet);

    DebugExitVOID(S20_FreeDataPkt);
}

//
// S20_SendDataPkt - see s20.h
//
void  S20_SendDataPkt
(
    UINT            streamID,
    UINT            nodeID,
    PS20DATAPACKET  pS20Packet
)
{
    UINT            rc;
    NET_PRIORITY    priority;

    DebugEntry(S20_SendDataPkt);

    priority = S20StreamToS20Priority(streamID);

    //
    // Note:
    // Sends to an individual are not flow-controlled.  Only sends to
    // everybody on the global app sharing channel are.
    //

    //
    // Try to send queued control packets first.
    //
    rc = S20SendQueuedControlPackets();
    if (rc == 0)
    {
        //
        // Fill in the stream, length and correlator before sending.
        //
        pS20Packet->stream      = (BYTE)streamID;
        pS20Packet->correlator  = g_s20ShareCorrelator;

        //
        // dataLength includes the DATAPACKETHEADER part of the S20DATAPACKET
        // structure
        //
        TRACE_OUT(("S20_SendPkt: sending data packet size %d",
            pS20Packet->dataLength + sizeof(S20DATAPACKET) - sizeof(DATAPACKETHEADER)));

        rc = MG_SendData(g_s20pmgClient, priority, (NET_CHANNEL_ID)nodeID,
            pS20Packet->dataLength + sizeof(S20DATAPACKET) - sizeof(DATAPACKETHEADER),
            (void **)&pS20Packet);
    }

    // lonchanc: it is ok for MG_SendData returns 0 and NET_CHANNEL_EMPTY

    if (rc == NET_RC_MGC_NOT_CONNECTED)
    {
        S20LeaveOrEndShare();
    }
    else
    {
        if (rc != 0)
        {
            ERROR_OUT(("SendData rc=%lx - expecting share termination soon", rc));
        }
    }

    DebugExitVOID(S20_SendDataPkt);
}


//
// S20_UTEventProc()
//
BOOL CALLBACK  S20_UTEventProc
(
    LPVOID      userData,
    UINT        event,
    UINT_PTR    data1,
    UINT_PTR    data2
)
{
    BOOL        processed;

    DebugEntry(S20_UTEventProc);

    processed = TRUE;

    switch (event)
    {
        case NET_EVENT_USER_ATTACH:
            S20AttachConfirm(LOWORD(data1), HIWORD(data1), (UINT)data2);
            break;

        case NET_EVENT_USER_DETACH:
            S20DetachIndication(LOWORD(data1), (UINT)data2);
            break;

        case NET_EVENT_CHANNEL_JOIN:
            S20JoinConfirm((PNET_JOIN_CNF_EVENT)data2);
            MG_FreeBuffer(g_s20pmgClient, (void **)&data2);
            break;

        case NET_EVENT_CHANNEL_LEAVE:
            S20LeaveIndication(LOWORD(data1), (UINT)data2);
            break;

        case NET_EVENT_DATA_RECEIVED:
            S20SendIndication((PNET_SEND_IND_EVENT)data2);
            break;

        case NET_FLOW:
            //
            // Handle the feedback event.
            //
            S20Flow((UINT)data1, (UINT)data2);
            break;

        case CMS_NEW_CALL:
            if (g_asSession.scState == SCS_INIT)
            {
                //
                // This happens when (a) a new real call is started
                // (b) creating a new share in a call fails, so we want to
                // then try to join an existing share.
                //
                SCCheckForCMCall();
            }
            break;

        case CMS_END_CALL:
            if (g_asSession.callID)
            {
                //
                // AS lock protects g_asSession global fields
                //
                TRACE_OUT(("AS LOCK:  CMS_END_CALL"));
                UT_Lock(UTLOCK_AS);

                g_asSession.gccID  = 0;
                g_asSession.callID = 0;

                UT_Unlock(UTLOCK_AS);
                TRACE_OUT(("AS UNLOCK:  CMS_END_CALL"));

                if (g_asSession.scState > SCS_SHAREENDING)
                {
                    SC_EndShare();
                }

                DCS_NotifyUI(SH_EVT_APPSHARE_READY, FALSE, 0);

                g_s20BroadcastID = 0;
                g_s20JoinedLocal = FALSE;
                g_s20State = S20_INIT;
                TRACE_OUT(("g_s20State is S20_INIT"));
            }
            break;

        default:
            processed = FALSE;
            break;
    }

    DebugExitBOOL(S20_UTEventProc, processed);
    return(processed);
}



//
// FUNCTION: S20AttachUser
//
// DESCRIPTION:
//
// Called when we want to attach this sets up the various parameters for
// MG_Attach, calls it and handles the return codes from NET.
//
// PARAMETERS:
//
// callID - the callID provided by the SC user
//
//
//
const NET_FLOW_CONTROL c_S20FlowControl =
    {
        // latency
        {
            S20_LATENCY_TOP_PRIORITY,
            S20_LATENCY_HIGH_PRIORITY,
            S20_LATENCY_MEDIUM_PRIORITY,
            S20_LATENCY_LOW_PRIORITY
        },
        // stream size
        {
            S20_SIZE_TOP_PRIORITY,
            S20_SIZE_HIGH_PRIORITY,
            S20_SIZE_MEDIUM_PRIORITY,
            S20_SIZE_LOW_PRIORITY
        }
    };


//
// S20CreateOrJoinShare()
// Creates a share for the first time or joins an existing one
//
// Normally, creating a share requires
//      * registration
//      * broadcast of S20_CREATE packet
//      * reception of one S20_RESPOND packet
// for the share to be created.  However, if we're the top provider, we
// assume it's created without waiting for an S20_RESPOND.  If something
// goes wrong later, it will clean itself up anyway.  Then that allows us
// to host a conference, share an app, and have it be shared through the
// life of the conference, even if remotes call/hang up repeatedly.
//
BOOL S20CreateOrJoinShare
(
    UINT    what,
    UINT    callID
)
{
    UINT    rc = 0;
    BOOL    noFlowControl;
    NET_FLOW_CONTROL    flowControl;

    DebugEntry(S20CreateOrJoinShare);

    ASSERT((what == S20_CREATE) || (what == S20_JOIN));

    switch (g_s20State)
    {
        case S20_INIT:
            //
            // Remember what to do when we have attached and joined.
            //
            g_s20Pend = what;

            //
            // ATTACH the S20 MCS USER
            //

            COM_ReadProfInt(DBG_INI_SECTION_NAME, S20_INI_NOFLOWCONTROL,
                FALSE, &noFlowControl);
            if (noFlowControl)
            {
                WARNING_OUT(("S20 Flow Control is OFF"));
                ZeroMemory(&flowControl, sizeof(flowControl));
            }
            else
            {
                // Set up our target latencies and stream sizes
                flowControl = c_S20FlowControl;
            }

            //
            // Initiate an attach - the domain equals the callID.
            //
            rc = MG_Attach(g_s20pmgClient, callID, &flowControl);
            if (rc == 0)
            {
                //
                // Make the state change if we succeeded
                //
                g_s20State = S20_ATTACH_PEND;
                TRACE_OUT(("g_s20State is S20_ATTACH_PEND"));
            }
            else
            {
                //
                // End the share immediately and no state change.
                //
                WARNING_OUT(("MG_Attach of S20 User failed, rc = %u", rc));

                g_s20Pend = 0;
                SC_End();
            }
            break;

        case S20_ATTACH_PEND:
        case S20_JOIN_PEND:
            //
            // We just need to set the flag in these cases - we will try
            // to create a share when we have attached and joined our
            // channel.
            //
            g_s20Pend = what;
            break;

        case S20_SHARE_PEND:
            //
            // If a share is pending but the SC user wants to create
            // or join again we let them.  Multiple outstanding joins are
            // benign and another create will have a new correlator so the
            // previous one (and any responses to it) will be obsolete.
            //
            // NOTE DELIBERATE FALL THROUGH
            //
            //

        case S20_NO_SHARE:
            TRACE_OUT(("S20_NO_SHARE"));
            //
            // Broadcast a S20CREATE packet.
            //
            if (what == S20_CREATE)
            {
                g_s20ShareCorrelator = S20NewCorrelator();
                TRACE_OUT(("CP CREATE %lu %d", g_s20ShareCorrelator, 0));
                rc = S20FlushAndSendControlPacket(what,
                                                  g_s20ShareCorrelator,
                                                  0,
                                                  NET_TOP_PRIORITY);
            }
            else
            {
                g_s20ShareCorrelator = 0;
                TRACE_OUT(("CP JOIN %lu %d", 0, 0));
                rc = S20FlushAndSendControlPacket(what, 0, 0,
                                                  NET_TOP_PRIORITY);
            }
            TRACE_OUT(("S20FlushAndSendControlPacket %u", rc));

            if (rc == 0)
            {
                //
                // Switch state.
                //
                g_s20State = S20_SHARE_PEND;
                TRACE_OUT(("g_s20State is S20_SHARE_PEND"));

                //
                // Assume success right away when creating the share.  We'll
                // hear back in a bit if there's a problem.
                //
                if (what == S20_CREATE)
                {
                    // Don't check for top provider, assume success always.
                    TRACE_OUT(("S20: Creating share, assume success"));

                    //
                    // LAURABU -- IF THIS CAUSES PROBLEMS, fall back to
                    // checking for one person only in call.
                    //
                    if (!SC_Start(g_s20LocalID))
                    {
                        WARNING_OUT(("S20CreateOrJoin: couldn't start share"));
                        SC_End();
                    }
                    else
                    {
                        g_s20State = S20_IN_SHARE;
                        TRACE_OUT(("g_s20State is S20_IN_SHARE"));
                    }
                }
            }
            else
            {
                //
                // Something failed so we will just forget about the share.
                //
                WARNING_OUT(("Failed to create share"));
                if (what == S20_CREATE)
                {
                    SC_End();
                }
            }
            break;

        default:
            ERROR_OUT(("Invalid state %u for %u", g_s20State, what));
    }

    DebugExitBOOL(S20CreateOrJoinShare, (rc == 0));
    return(rc == 0);
}

//
// FUNCTION: S20LeaveOrEndShare
//
// DESCRIPTION:
//
// Handles processing a S20_LeaveShare or a S20_EndShare call.
//
// PARAMETERS:
//
// what - what to do (the protocol packet type corresponding to the
// action).
//
// RETURNS: NONE
//
//
void S20LeaveOrEndShare(void)
{
    UINT    what;

    DebugEntry(S20LeaveOrEndShare);

    //
    // The share is destroyed whenever the creator leaves.  Nobody else
    // can end it.
    //
    if (S20_GET_CREATOR(g_s20ShareCorrelator) == g_s20LocalID)
    {
        what = S20_END;
    }
    else
    {
        what = S20_LEAVE;
    }

    ASSERT(what == S20_LEAVE || what == S20_END);

    switch (g_s20State)
    {
        case S20_ATTACH_PEND:
        case S20_JOIN_PEND:
            //
            // We just need to reset the pending flags here - no state
            // change required.
            //
            g_s20Pend = 0;
            break;

        case S20_IN_SHARE:
        case S20_SHARE_PEND:
            TRACE_OUT(("S20_SHARE_PEND"));
            //
            // Now try and make and send the appropriate control packet.
            //
            TRACE_OUT(("CP %u %u %d", what, g_s20ShareCorrelator, 0));
            S20FlushSendOrQueueControlPacket(what,
                                             g_s20ShareCorrelator,
                                             0,
                                             NET_TOP_PRIORITY);

            //
            // Make the SHARE_ENDED callback.
            //
            SC_End();
            break;

        default:
            WARNING_OUT(("invalid state %d for %d", g_s20State, what));
            break;
    }

    DebugExitVOID(S20LeaveOrEndShare);
}

//
// FUNCTION: S20MakeControlPacket
//
// DESCRIPTION:
//
// Attempts to allocate and construct a S20 control packet.
//
// PARAMETERS:
//
// what - which type of packet
//
// correlator - the correlator to place in the packet
//
// who - the target party (if what is a S20_DELETE) or the originator (if
// what is S20_RESPOND)
//
// ppPacket - where to return a pointer to the packet.
//
// pLength - where to return the length of the packet.
//
// priority - priority of packet to make
//
// RETURNS:
//
//
UINT S20MakeControlPacket
(
    UINT            what,
    UINT            correlator,
    UINT            who,
    PS20PACKETHEADER * ppPacket,
    LPUINT          pcbPacketSize,
    UINT            priority
)
{
    UINT      rc;
    BOOL      fPutNameAndCaps;
    UINT      cbPacketSize;
    UINT      personNameLength;
    PS20PACKETHEADER  pS20Packet = NULL;
    LPBYTE    pData;

    DebugEntry(S20MakeControlPacket);

    //
    // Assume success
    //
    rc = 0;

    //
    // Work out how big the packet needs to be.  Start with the fixed
    // length then add in capabilities and our name (if they are required).
    //
    switch (what)
    {
        case S20_CREATE:
            cbPacketSize = sizeof(S20CREATEPACKET) - 1;
            fPutNameAndCaps = TRUE;
            break;

        case S20_JOIN:
            cbPacketSize = sizeof(S20JOINPACKET) - 1;
            fPutNameAndCaps = TRUE;
            break;

        case S20_RESPOND:
            cbPacketSize = sizeof(S20RESPONDPACKET) - 1;
            fPutNameAndCaps = TRUE;
            break;

        case S20_DELETE:
            cbPacketSize = sizeof(S20DELETEPACKET) - 1;
            fPutNameAndCaps = FALSE;
            break;

        case S20_LEAVE:
            cbPacketSize = sizeof(S20LEAVEPACKET);
            fPutNameAndCaps = FALSE;
            break;

        case S20_END:
            cbPacketSize = sizeof(S20ENDPACKET) - 1;
            fPutNameAndCaps = FALSE;
            break;

        case S20_COLLISION:
            cbPacketSize = sizeof(S20COLLISIONPACKET);
            fPutNameAndCaps = FALSE;
            break;

        default:
            ERROR_OUT(("BOGUS S20 packet %u", what));
            break;
    }

    if (fPutNameAndCaps)
    {
        ASSERT(g_asSession.gccID);
        ASSERT(g_asSession.cchLocalName);

        //
        // The name data is always dword aligned (including the NULL)
        //
        personNameLength = DC_ROUND_UP_4(g_asSession.cchLocalName+1);
        cbPacketSize += personNameLength + sizeof(g_cpcLocalCaps);
    }

    //
    // Now try to allocate a buffer for this.
    //
    rc = MG_GetBuffer( g_s20pmgClient,
                       cbPacketSize,
                           (NET_PRIORITY)priority,
                           g_s20BroadcastID,
                           (void **)&pS20Packet );

    if (rc != 0)
    {
        TRACE_OUT(("MG_GetBuffer failed; can't send S20 control packet"));
        DC_QUIT;
    }

    pS20Packet->packetType  = (TSHR_UINT16)what | S20_ALL_VERSIONS;
    pS20Packet->user        = g_s20LocalID;

    //
    // This will point to where we need to stuff the name and/or
    // capabilities.
    //
    pData = NULL;

    //
    // Now do the packet dependant fields.
    //
    switch (what)
    {
        case S20_CREATE:
        {
            ASSERT(fPutNameAndCaps);
            ((PS20CREATEPACKET)pS20Packet)->correlator  = correlator;
            ((PS20CREATEPACKET)pS20Packet)->lenName     = (TSHR_UINT16)personNameLength;
            ((PS20CREATEPACKET)pS20Packet)->lenCaps     = (TSHR_UINT16)sizeof(g_cpcLocalCaps);
            pData = ((PS20CREATEPACKET)pS20Packet)->data;
        }
        break;

        case S20_JOIN:
        {
            ASSERT(fPutNameAndCaps);
            ((PS20JOINPACKET)pS20Packet)->lenName       = (TSHR_UINT16)personNameLength;
            ((PS20JOINPACKET)pS20Packet)->lenCaps       = (TSHR_UINT16)sizeof(g_cpcLocalCaps);
            pData = ((PS20JOINPACKET)pS20Packet)->data;
        }
        break;

        case S20_RESPOND:
        {
            ASSERT(fPutNameAndCaps);
            ((PS20RESPONDPACKET)pS20Packet)->correlator = correlator;
            ((PS20RESPONDPACKET)pS20Packet)->originator = (TSHR_UINT16)who;
            ((PS20RESPONDPACKET)pS20Packet)->lenName    = (TSHR_UINT16)personNameLength;
            ((PS20RESPONDPACKET)pS20Packet)->lenCaps    = (TSHR_UINT16)sizeof(g_cpcLocalCaps);
            pData = ((PS20RESPONDPACKET)pS20Packet)->data;
        }
        break;

        case S20_DELETE:
        {
            ASSERT(!fPutNameAndCaps);
            ((PS20DELETEPACKET)pS20Packet)->correlator = correlator;
            ((PS20DELETEPACKET)pS20Packet)->target = (TSHR_UINT16)who;
            ((PS20DELETEPACKET)pS20Packet)->lenName = 0;
        }
        break;

        case S20_LEAVE:
        {
            ASSERT(!fPutNameAndCaps);
            ((PS20LEAVEPACKET)pS20Packet)->correlator = correlator;
        }
        break;

        case S20_END:
        {
            ASSERT(!fPutNameAndCaps);
            ((PS20ENDPACKET)pS20Packet)->correlator = correlator;
            ((PS20ENDPACKET)pS20Packet)->lenName    = 0;
        }
        break;

        case S20_COLLISION:
        {
            ASSERT(!fPutNameAndCaps);
            ((PS20COLLISIONPACKET)pS20Packet)->correlator = correlator;
        }
        break;

        default:
        {
            ERROR_OUT(("Invalid type %u", what));
        }
        break;
    }

    //
    // Now we can copy in the name and capabilities.
    //
    if (fPutNameAndCaps)
    {
        lstrcpy((LPSTR)pData, g_asSession.achLocalName);

        // The local name is always null-terminated (truncated to fit in 48 bytes inc. null)
        pData += personNameLength;

        memcpy(pData, &g_cpcLocalCaps, sizeof(g_cpcLocalCaps));

        //
        // FILL IN GCC-ID HERE; the local caps are shared but the local
        // person entity in the share doesn't exist yet.
        //
        ((CPCALLCAPS *)pData)->share.gccID = g_asSession.gccID;
    }

    //
    // Return the packet and length.
    //
    *ppPacket       = pS20Packet;
    *pcbPacketSize  = cbPacketSize;

DC_EXIT_POINT:
    DebugExitDWORD(S20MakeControlPacket, rc);
    return(rc);
}

//
// FUNCTION: S20FlushSendOrQueueControlPacket
//
// DESCRIPTION:
//
// Attempts to flush any queued S20 control packets and then attempte to
// send a S20 control packet.  If this fails the queue the packet (the
// actual packet is freed.
//
// PARAMETERS:
//
// what - which type of packet
//
// correlator - the correlator to place in the packet
//
// who - the target party (if what is a S20_DELETE) or the originator (if
// what is S20_RESPOND)
//
// priority - priority to send packet at
//
// RETURNS: NONE
//
//
UINT S20FlushSendOrQueueControlPacket(
    UINT      what,
    UINT      correlator,
    UINT      who,
    UINT      priority)
{
    UINT rc;

    DebugEntry(S20FlushSendOrQueueControlPacket);

    rc = S20FlushAndSendControlPacket(what, correlator, who, priority);
    if (rc != 0)
    {
        // Let's queue this packet
        if (((g_s20ControlPacketQTail + 1) % S20_MAX_QUEUED_CONTROL_PACKETS) ==
                                                            g_s20ControlPacketQHead)
        {
            //
            // There is no more space in the control packet queue.  We will
            // discard everything from it and say the share ended because of
            // a network error (if we're in a share).
            //
            ERROR_OUT(("No more space in control packet queue"));
        }
        else
        {
            S20CONTROLPACKETQENTRY *p = &(g_s20ControlPacketQ[g_s20ControlPacketQTail]);

            p->who        = who;
            p->correlator = correlator;
            p->what       = what;
            p->priority   = priority;

            g_s20ControlPacketQTail = (g_s20ControlPacketQTail + 1) %
                                                   S20_MAX_QUEUED_CONTROL_PACKETS;
            rc = 0;
        }
    }

    DebugExitDWORD(S20FlushSendOrQueueControlPacket, rc);
    return rc;
}


//
// FUNCTION: S20FlushAndSendControlPacket
//
// DESCRIPTION:
//
// Attempts to flush any queued S20 control packets and then send a S20
// control packet.  If sending fails then free the packet.
//
// PARAMETERS:
//
// what - which type of packet
//
// correlator - the correlator to place in the packet
//
// who - the target party (if what is a S20_DELETE) or the originator (if
// what is S20_RESPOND)
//
// priority - priority to send packet at
//
// RETURNS:
//
//
UINT S20FlushAndSendControlPacket
(
    UINT        what,
    UINT        correlator,
    UINT        who,
    UINT        priority
)
{
    UINT        rc;
    PS20PACKETHEADER  pS20Packet;
    UINT      length;

    DebugEntry(S20FlushAndSendControlPacket);

    //
    // First try to flush.
    //
    rc = S20SendQueuedControlPackets();
    if (rc != 0)
    {
        TRACE_OUT(("S20SendQueuedControlPackets %u", rc));
        DC_QUIT;
    }

    rc = S20MakeControlPacket(what, correlator, who, &pS20Packet, &length, priority);
    if (rc != 0)
    {
        TRACE_OUT(("S20MakeControlPacket %u", rc));
        DC_QUIT;
    }

    TRACE_OUT(("CP %u %lu %u sent", what, correlator, who));

    rc = S20SendControlPacket(pS20Packet, length, priority);
    if (rc != 0)
    {
        TRACE_OUT(("S20SendControlPacket %u", rc));
        DC_QUIT;
    }

DC_EXIT_POINT:
    DebugExitDWORD(S20FlushAndSendControlPacket, rc);
    return(rc);
}

//
// FUNCTION: S20SendControlPacket
//
// DESCRIPTION:
//
// Attempts to send a S20 control packet.  If sending fails then free the
// packet.
//
// PARAMETERS:
//
// pPacket - pointer to the control packet to send.  These are always
// broadcast.
//
// length - length of aforementioned packet.
//
// priority - priority to send packet at
//
// RETURNS:
//
//
UINT S20SendControlPacket
(
    PS20PACKETHEADER    pS20Packet,
    UINT                length,
    UINT                priority
)
{
    UINT rc;

    DebugEntry(S20SendControlPacket);

    TRACE_OUT(("S20SendControlPacket: sending packet type %x, size %d",
        pS20Packet->packetType, length));

    rc = MG_SendData( g_s20pmgClient,
                          (NET_PRIORITY)priority,
                          g_s20BroadcastID,
                          length,
                          (void **)&pS20Packet );
    if (rc != 0)
    {
        TRACE_OUT(("MG_SendData %lx", rc));
    }

    if (pS20Packet != NULL)
    {
        //
        // The packet was not freed by the NL - we will do it instead.
        //
        MG_FreeBuffer(g_s20pmgClient, (void **)&pS20Packet);
    }

    DebugExitDWORD(S20SendControlPacket, rc);
    return(rc);
}


//
// FUNCTION: S20SendQueuedControlPackets
//
// DESCRIPTION:
//
// Sends as many queued packets as possible
//
// PARAMETERS:
//
//
// RETURNS:
//
//  0 - all queued packets have been sent.
//
//
UINT S20SendQueuedControlPackets(void)
{
    PS20PACKETHEADER    pS20Packet;
    UINT                length;
    UINT                rc;
    UINT                priority;

    DebugEntry(S20SendQueuedControlPackets);

    //
    // Assume success until something fails.
    //
    rc = 0;

    //
    // While there are packets to send - try to send them
    //
    while (g_s20ControlPacketQTail != g_s20ControlPacketQHead)
    {
        S20CONTROLPACKETQENTRY *p = &(g_s20ControlPacketQ[g_s20ControlPacketQHead]);
        priority = p->priority;

        rc = S20MakeControlPacket(p->what, p->correlator, p->who,
                                      &pS20Packet, &length, priority);
        if (rc != 0)
        {
            //
            // Failed to make the packet - give up.
            //
            WARNING_OUT(("S20MakeControlPacket failed error %u", rc));
            break;
        }

        rc = S20SendControlPacket(pS20Packet, length, priority);
        if (rc != 0)
        {
            //
            // Failed to send the packet - give up.
            //
            break;
        }

        //
        // Succesfully sent the queue packet - move the head of the queue
        // along one.
        //
        g_s20ControlPacketQHead = (g_s20ControlPacketQHead + 1) %
                                               S20_MAX_QUEUED_CONTROL_PACKETS;
    }

    DebugExitDWORD(S20SendQueuedControlPackets, rc);
    return(rc);
}


//
// S20AttachConfirm()
//
// Handles the MCS attach confirmation
//
void S20AttachConfirm
(
    NET_UID         userId,
    NET_RESULT      result,
    UINT            callID
)
{
    NET_CHANNEL_ID  correlator;
    UINT            rc;

    DebugEntry(S20AttachConfirm);

    if (g_s20State == S20_ATTACH_PEND)
    {
        //
        // Assume we need to clear up.
        //
        rc = NET_RC_S20_FAIL;

        if (result == NET_RESULT_OK)
        {
            //
            // We're in.  Now try to join our channel and remember our
            // userID.
            //
            g_s20LocalID = userId;

            //
            // We must join our single member channel for flow control
            //
            rc = MG_ChannelJoin(g_s20pmgClient,
                                    &correlator,
                                    g_s20LocalID);
            if (rc == 0)
            {
                //
                // Now join the broadcast channel
                //
                rc = MG_ChannelJoinByKey(g_s20pmgClient,
                                             &correlator,
                                             GCC_AS_CHANNEL_KEY);
                if (rc != 0)
                {
                    MG_ChannelLeave(g_s20pmgClient, g_s20LocalID);
                }

            }

            if (rc == 0)
            {
                //
                // It worked - make the state change.
                //
                g_s20State = S20_JOIN_PEND;
                TRACE_OUT(("g_s20State is S20_JOIN_PEND"));
            }
            else
            {
                //
                // Everything else is some sort of logic error (we will
                // follow our recovery path).
                //
                WARNING_OUT(("ChannelJoin unexpected error %u", rc));
            }
        }

        if (rc != 0)
        {
            //
            // Something didn't work work out - clear up with a
            // SHARE_ENDED if a create or join was pending.
            //

            if (result == NET_RESULT_OK)
            {
                //
                // The attach succeeded so detach because the join
                // failed and we want to go back to initialised state.
                //
                MG_Detach(g_s20pmgClient);
                g_s20LocalID = 0;
            }

            //
            // Now make the state change and generate event if
            // necessary.
            //
            g_s20State = S20_INIT;
            TRACE_OUT(("g_s20State is S20_INIT"));

            if (g_s20Pend)
            {
                g_s20Pend = 0;
                SC_End();
            }

        }
    }

    DebugExitVOID(S20AttachConfirm);
}



//
// S20DetachIndication()
//
// Handles NET_EVENT_DETACH notification for a user
//
void  S20DetachIndication
(
    NET_UID     userId,
    UINT        callID
)
{
    DebugEntry(S20DetachIndication);

    //
    // There are three possibilities here
    //
    //  1.  We have been forced out.
    //  2.  All remote users have detached.
    //  3.  A remote user has detached.
    //
    // 2 is effectively a 3 for each current remote user.  We report 1 as a
    // network error.
    //
    if (userId == g_s20LocalID)
    {
        //
        // We have been forced out.
        //
        switch (g_s20State)
        {
            case S20_IN_SHARE:
            case S20_SHARE_PEND:
            case S20_SHARE_STARTING:
                //
                // Generate a share ended event.
                //
                SC_End();

                // FALL THROUGH
            case S20_NO_SHARE:
                //
                // Just revert to init state.
                //
                g_s20State = S20_INIT;
                TRACE_OUT(("g_s20State is S20_INIT"));
                break;

            case S20_JOIN_PEND:
            case S20_ATTACH_PEND:
                //
                // Check the join or create pending flags here and if
                // either one is set then generate a share ended
                //
                if (g_s20Pend)
                {
                    g_s20Pend = 0;
                    SC_End();
                }

                g_s20State = S20_INIT;
                TRACE_OUT(("g_s20State is S20_INIT"));
                break;

            case S20_TERM:
            case S20_INIT:
                //
                // Unusual but not impossible.
                //
                TRACE_OUT(("Ignored in state %u", g_s20State));
                break;

            default:
                ERROR_OUT(("Invalid state %u", g_s20State));
                break;
        }

    }
    else
    {
        ASSERT(userId != NET_ALL_REMOTES);

        //
        // A single remote user has left.
        //
        switch (g_s20State)
        {
            case S20_IN_SHARE:
            {
                //
                // If we are in a share then issue a PARTY_DELETED event
                // for the appropriate party if they have been added.
                // S20MaybeIssuePersonDelete will only issue deletes for
                // parties which have been added succesfully.
                //
                S20MaybeIssuePersonDelete(userId);
            }
            break;

            default:
            {
                //
                // In any other state this makes no difference to us.
                //
                TRACE_OUT(("ignored in state %u", g_s20State));
            }
            break;
        }
    }

    DebugExitVOID(S20DetachIndication);
}


//
// FUNCTION: S20JoinConfirm
//
// DESCRIPTION:
//
// Handles the NET_EVENT_CHANNEL_JOIN message from the NL
//
// PARAMETERS:
//
// pNetEventHeader - pointer to the event
//
// RETURNS: NONE
//
//
void  S20JoinConfirm(PNET_JOIN_CNF_EVENT pJoinConfirm)
{
    UINT             rc;

    DebugEntry(S20JoinConfirm);

    if (g_s20State == S20_JOIN_PEND)
    {
        //
        // Handle the join completing
        //
        if (pJoinConfirm->result == NET_RESULT_OK)
        {
            //
            // We have sucessfully joined, either our single user
            // channel or our broadcast channel
            // We detect that both are successful when the g_s20BroadcastID
            // field is filled in and g_s20JoinedLocal is TRUE
            //
            if (pJoinConfirm->channel == g_s20LocalID)
            {
                g_s20JoinedLocal = TRUE;
                TRACE_OUT(("Joined user channel"));
            }
            else
            {
                //
                // Store the assigned channel.
                //
                g_s20BroadcastID = pJoinConfirm->channel;
                TRACE_OUT(("Joined channel %u", (UINT)g_s20BroadcastID));
            }

            //
            // If we have joined both channels then let it rip
            //
            if (g_s20JoinedLocal && g_s20BroadcastID)
            {
                g_s20State = S20_NO_SHARE;
                TRACE_OUT(("g_s20State is S20_NO_SHARE"));

                DCS_NotifyUI(SH_EVT_APPSHARE_READY, TRUE, 0);

                //
                // Issue create or join if they are pending.
                //
                if (g_s20Pend != 0)
                {
                    ASSERT(g_s20Pend == S20_JOIN);

                    UINT sPend;

                    sPend = g_s20Pend;
                    g_s20Pend = 0;
                    S20CreateOrJoinShare(sPend, pJoinConfirm->callID);
                }
            }
        }
        else
        {
            ERROR_OUT(("Channel join failed"));

            //
            // Clear up by reverting to initialised state.
            //
            MG_Detach(g_s20pmgClient);

            g_s20LocalID  = 0;
            g_s20BroadcastID = 0;
            g_s20JoinedLocal = FALSE;

            //
            // Now make the state change and generate event if
            // necessary.
            //
            g_s20State = S20_INIT;
            TRACE_OUT(("g_s20State is S20_INIT"));

            if (g_s20Pend)
            {
                g_s20Pend = 0;
                SC_End();
            }
        }
    }
    DebugExitVOID(S20JoinConfirm);
}

//
// FUNCTION: S20LeaveIndication
//
// DESCRIPTION:
//
// Handles the NET_EV_LEAVE_INDICATION message from the NL
//
// PARAMETERS:
//
// pNetEventHeader - pointer to the event
//
// RETURNS: NONE
//
//
void  S20LeaveIndication
(
    NET_CHANNEL_ID  channelID,
    UINT            callID
)
{
    UINT rc;

    DebugEntry(S20LeaveIndication);

    //
    // A leave indication means we were forced out of a channel.  As we
    // only use one channel this is bound to be terminal and we will
    // generate appropriate share ending type events and detach (this will
    // hopefully tell the remote systems we have gone - also we have no
    // state which is attached but not trying to join so the alternatives
    // would be to 1) add a new state or 2) try and re-join a channel
    // immediately we get chucked out.  Neither appeals.
    //
    switch (g_s20State)
    {
        case S20_IN_SHARE:
        case S20_SHARE_PEND:
        case S20_SHARE_STARTING:
            //
            // Generate a share ended event.
            //
            SC_End();

            // FALL THROUGH

        case S20_NO_SHARE:
        case S20_JOIN_PEND:
        case S20_ATTACH_PEND:
            //
            // Detach from the domain.
            //
            MG_Detach(g_s20pmgClient);
            g_s20LocalID = 0;

            //
            // Check the join or create pending flags here and if either
            // one is set then generate a share ended
            //
            if (g_s20Pend)
            {
                g_s20Pend = 0;
                SC_End();
            }

            g_s20State = S20_INIT;
            TRACE_OUT(("g_s20State is S20_INIT"));
            break;

        case S20_TERM:
        case S20_INIT:
            //
            // Unusual but not impossible.
            //
            TRACE_OUT(("Ignored in state %u", g_s20State));
            break;

        default:
            ERROR_OUT(("Invalid state %u", g_s20State));
            break;
    }

    DebugExitVOID(S20LeaveIndication);
}


//
// S20SendIndication()
//
// Handles received data notification
//
void  S20SendIndication(PNET_SEND_IND_EVENT pSendIndication)
{
    PS20PACKETHEADER        pS20Packet;

    DebugEntry(S20SendIndication);

    pS20Packet = (PS20PACKETHEADER)(pSendIndication->data_ptr);

	//
	// If App Sharing detaches from the T.120 conference, it will free up
	// all data indication buffers.  We need to check for this condition.
	//
    if (NULL != pS20Packet)
    {
	    if (!(pS20Packet->packetType & S20_ALL_VERSIONS))
	    {
	        ERROR_OUT(("User is trying to connect from %#hx system",
	                 pS20Packet->packetType & 0xF0));

	        //
	        // This should never happen, but if it does then we assert in the
	        // debug build and quietly fail in the retail build.
	        //
	        ERROR_OUT(("An unsupported version of app sharing joined the conference"));
	        DC_QUIT;
	    }

	    //
	    // Mask out the protocol version
	    //
	    switch (pS20Packet->packetType & S20_PACKET_TYPE_MASK)
	    {
	        case S20_CREATE:
	            S20CreateMsg((PS20CREATEPACKET)pS20Packet);
	            break;

	        case S20_JOIN:
	            S20JoinMsg((PS20JOINPACKET)pS20Packet);
	            break;

	        case S20_RESPOND:
	            S20RespondMsg((PS20RESPONDPACKET)pS20Packet);
	            break;

	        case S20_DELETE:
	            S20DeleteMsg((PS20DELETEPACKET)pS20Packet);
	            break;

	        case S20_LEAVE:
	            S20LeaveMsg((PS20LEAVEPACKET)pS20Packet);
	            break;

	        case S20_END:
	            S20EndMsg((PS20ENDPACKET)pS20Packet);
	            break;

            case S20_COLLISION:
                S20CollisionMsg((PS20COLLISIONPACKET)pS20Packet);
                break;

	        case S20_DATA:
	            S20DataMsg((PS20DATAPACKET)pS20Packet);
	            break;

	        default:
	            ERROR_OUT(("invalid packet %hu", pS20Packet->packetType));
	            break;
	    }
    }

DC_EXIT_POINT:
    MG_FreeBuffer(g_s20pmgClient, (void **)&pSendIndication);

    DebugExitVOID(S20SendIndication);
}


//
// FUNCTION: S20Flow
//
// DESCRIPTION:
//
// Handles the NET_FLOW event
//
// PARAMETERS:
//
// data1, data2 - the data from the UT event handler
//
// RETURNS: NONE
//
//
void S20Flow
(
    UINT    priority,
    UINT    newBufferSize
)
{
    DebugEntry(S20Flow);

    //
    // We know this is our data channel (it is the only one we flow
    // control) but if this is not the UPDATE stream, then ignore it.
    // UPDATEs are low priority.
    //
    ASSERT(priority == NET_LOW_PRIORITY);

    if (g_asSession.pShare != NULL)
    {
        TRACE_OUT(("Received flow control notification, new size %lu",
               newBufferSize));

        if (g_asSession.pShare->m_pHost != NULL)
        {
            //
            // First try and improve the LAN performance by sending orders in
            // large buffers, if we find that the throughput can handle it.
            //
            g_asSession.pShare->m_pHost->UP_FlowControl(newBufferSize);

            //
            // Adjust the depth which we try to spoil orders to based on
            // feedback.
            //
            g_asSession.pShare->m_pHost->OA_FlowControl(newBufferSize);
        }

        //
        // Tell DCS so that we can skip GDC compression.
        // This improves responsiveness over high bandwidth links because it
        // reduces the CPU loading on the sender
        //
        g_asSession.pShare->DCS_FlowControl(newBufferSize);
    }

    DebugExitVOID(S20Flow);
}

//
// FUNCTION: S20CreateMsg
//
// DESCRIPTION:
//
// Handles an incoming create message.
//
// PARAMETERS:
//
// pS20Packet - pointer to the create message itself
//
// RETURNS: NONE
//
void  S20CreateMsg
(
    PS20CREATEPACKET  pS20Packet
)
{
    BOOL    rc;

    DebugEntry(S20CreateMsg);

    TRACE_OUT(("S20_CREATE from [%d - %s], correlator %x",
        pS20Packet->header.user, (LPSTR)pS20Packet->data,
        pS20Packet->correlator));

    //
    // First of all check if the correlator on this CREATE is the same as
    // our current view of the correlator.  This may happen if a sweep
    // RESPOND overtakes a CREATE - in this case we will create the share
    // on the RESPOND and this is simply the delayed CREATE arriving now so
    // we don't need to do anything here.
    //
    if (g_s20ShareCorrelator == pS20Packet->correlator)
    {
        WARNING_OUT(("Received S20_CREATE from [%d] with bogus correlator %x",
            pS20Packet->header.user, pS20Packet->correlator));
        DC_QUIT;
    }

    if ((g_s20State == S20_NO_SHARE) ||
        ((g_s20State == S20_SHARE_PEND) &&
         (g_s20ShareCorrelator == 0)))
    {
        //
        // Either there is no share or we have issued a join.  In these
        // curcumstances we want to try to accept the create message.
        //

        //
        // Remember the share correlator.
        //
        g_s20ShareCorrelator = pS20Packet->correlator;

        //
        // Start the share
        // CHECK FOR FAILURE FOR THE FIRST ONE.
        //
        rc = SC_Start(g_s20LocalID);
        if (rc)
        {
            g_s20State = S20_SHARE_STARTING;
            TRACE_OUT(("g_s20State is S20_SHARE_STARTING"));

            rc = S20MaybeAddNewParty(pS20Packet->header.user,
                pS20Packet->lenCaps, pS20Packet->lenName,
                pS20Packet->data);
        }

        if (!rc)
        {
            //
            // Something went wrong.  Kill the share, this will clean up
            // everything.
            //
            SC_End();
        }
    }
    else if ((g_s20State == S20_SHARE_PEND) ||
             (g_s20State == S20_SHARE_STARTING) ||
             (g_s20State == S20_IN_SHARE))
    {
        //
        // Only current share creator should tell other dude there's an
        // error.
        //
        if (S20_GET_CREATOR(g_s20ShareCorrelator) == g_s20LocalID)
        {
            //
            // If we know about a share already then ignore this one.
            //
            WARNING_OUT(("Received S20_CREATE from [%d] with correlator %x, share colllision",
                pS20Packet->header.user, pS20Packet->correlator));

            S20FlushSendOrQueueControlPacket(S20_END,
                pS20Packet->correlator, 0, NET_TOP_PRIORITY);
            S20FlushSendOrQueueControlPacket(S20_COLLISION,
                pS20Packet->correlator, 0, NET_TOP_PRIORITY);
        }
    }

DC_EXIT_POINT:
    DebugExitVOID(S20CreateMsg);
}

//
// FUNCTION: S20JoinMsg
//
// DESCRIPTION:
//
// Handles an incoming join message.
//
// PARAMETERS:
//
// pS20Packet - pointer to the join message itself
//
// RETURNS: NONE
//
void  S20JoinMsg
(
    PS20JOINPACKET  pS20Packet
)
{
    DebugEntry(S20JoinMsg);

    TRACE_OUT(("S20_JOIN from [%d - %s]",
        pS20Packet->header.user, (LPSTR)pS20Packet->data));

    switch (g_s20State)
    {
        case S20_SHARE_PEND:
            //
            // If we receive a join when a share is pending which we are
            // creating then we will try to add the party.  If it succeeds
            // then we will respond to the join as we would if we were in a
            // share (and we will indeed then be in a share).  If it fails
            // we will delete the joiner.
            //
            // If we receive a join when a share is pending because we are
            // trying to join (ie simultaneous joiners) then we can just
            // ignore it because a party which is joining a share will send
            // a respond as soon as they know the correlator for the share
            // they have succesfully joined.  This respond will be ignored
            // by any parties which saw and added the new party but it will
            // be seen by any simultaneous joiners and they will then get a
            // chance to try and add the other joiner.  If this fails they
            // will then do the normal processing for a failure handling a
            // respond message when we joined a share (ie delete
            // themselves).
            //
            // This will potentially mean that simultaneous joiners will
            // cause each other to delete themselves when there was room
            // for one of them in the share - we accept this.
            //

            //
            // Why is the share pending?  If the correlator is non-zero
            // then we are creating a share.
            //
            if (g_s20ShareCorrelator != 0)
            {
                //
                // We are creating a share - treat this like a respond.
                //
                if (!SC_Start(g_s20LocalID))
                {
                    WARNING_OUT(("S20Join: couldn't create share, clean up"));
                    SC_End();
                }
                else
                {
                    g_s20State = S20_SHARE_STARTING;
                    TRACE_OUT(("g_s20State is S20_SHARE_STARTING"));

                    S20MaybeAddNewParty(pS20Packet->header.user,
                        pS20Packet->lenCaps, pS20Packet->lenName,
                        pS20Packet->data);
                }
            }
            else
            {
                //
                // We are joining a share - simultaneous joiners.
                //
                TRACE_OUT(("Simultaneous joiner - ignored for now, expect a respond"));
            }
            break;

        case S20_IN_SHARE:
        case S20_SHARE_STARTING:
        {
            //
            // When we are in a share we will try and add this person then
            // give them a respond or a delete depending on what we did.
            //
            S20MaybeAddNewParty(pS20Packet->header.user,
                pS20Packet->lenCaps, pS20Packet->lenName,
                pS20Packet->data);
            break;
        }

        default:
            break;
    }

    DebugExitVOID(S20JoinMsg);
}


//
// FUNCTION: S20RespondMsg
//
// DESCRIPTION:
//
// Handles an incoming respond message.
//
// PARAMETERS:
//
// pS20Packet - pointer to the respond message itself
//
// RETURNS: NONE
//
void  S20RespondMsg
(
    PS20RESPONDPACKET  pS20Packet
)
{
    BOOL        rc;

    DebugEntry(S20RespondMsg);

    TRACE_OUT(("S20_RESPOND from [%d - %s], for [%d], correlator %x",
        pS20Packet->header.user, pS20Packet->data, pS20Packet->originator,
        pS20Packet->correlator));

    //
    // First filter the incoming respond messages as follows.
    //
    // If we know what share we are in and this does not have the same
    // correlator then respond with a delete and don't process any further.
    //
    // If the respond is not a response for us (ie we are not the
    // originator and it is not a sweep-up response (the originator equals
    // zero) then ignore it.
    //
    if ((g_s20ShareCorrelator != 0) &&
        (pS20Packet->correlator != g_s20ShareCorrelator))
    {
        //
        // Make sure sender knows we're not in this share.
        //
        TRACE_OUT(("S20_RESPOND from [%d] with unknown correlator %x",
            pS20Packet->header.user, pS20Packet->correlator));
        S20FlushSendOrQueueControlPacket(S20_LEAVE,
            pS20Packet->correlator, 0, NET_TOP_PRIORITY);
        DC_QUIT;
    }

    //
    // Now handle incoming message according to state.
    //
    switch (g_s20State)
    {
        case S20_SHARE_PEND:
            if ((pS20Packet->originator == g_s20LocalID) ||
                (pS20Packet->originator == 0))
            {
                //
                // A respond in share pending and it is for us.  First,
                // start a share.
                //
                rc = SC_Start(g_s20LocalID);
                if (!rc)
                {
                    SC_End();
                }
                else
                {
                    g_s20State = S20_SHARE_STARTING;
                    TRACE_OUT(("g_s20State is S20_SHARE_STARTING"));

                    //
                    // Why is the share pending?  If the correlator is non-zero
                    // then we are creating a share.
                    //
                    if (g_s20ShareCorrelator == 0)
                    {
                        //
                        // We are joining a share so do nothing if we fail (we
                        // will move back to NO_SHARE state if this happens).
                        //
                        g_s20ShareCorrelator = pS20Packet->correlator;
                    }

                    //
                    // Now try and add this new party.
                    //
                    rc = S20MaybeAddNewParty(pS20Packet->header.user,
                        pS20Packet->lenCaps, pS20Packet->lenName,
                        pS20Packet->data);

                    if (!rc)
                    {

                        //
                        // The responding party has been rejected by us.  What
                        // happens next depends on whether we are creating the
                        // share or not.
                        //
                        if (S20_GET_CREATOR(g_s20ShareCorrelator) != g_s20LocalID)
                        {
                            //
                            // We are not creating (ie we are joining) and we
                            // have failed to add a party so end the share
                            // (indicating that we are rejecting the remote
                            // party).
                            //
                            SC_End();
                        }

                        //
                        // If we were creating the share then there is nothing
                        // to do - just stay in SHARE_STARTING waiting for the
                        // next response.
                        //
                    }
                }
            }
            break;

        case S20_IN_SHARE:
        case S20_SHARE_STARTING:
            //
            // Who created this share.  If it was us then we want to
            // delete people we reject, otherwise we want to leave if we
            // reject people.
            //

            //
            // Now try and add this new party.  Of course it is entirely
            // possible that we've already added them at this stage - but
            // S20MaybeAddNewParty will just pretend to add them and return
            // if that's the case.
            //
            rc = S20MaybeAddNewParty(pS20Packet->header.user,
                pS20Packet->lenCaps, pS20Packet->lenName,
                pS20Packet->data);

            if (!rc)
            {
                WARNING_OUT(("Couldn't add [%d] to our share party list",
                    pS20Packet->header.user));
            }
            break;

        default:
            break;
    }

DC_EXIT_POINT:
    DebugExitVOID(S20RespondMsg);
}

//
// FUNCTION: S20DeleteMsg
//
// DESCRIPTION:
//
// Handles an incoming delete message.
//
// PARAMETERS:
//
// pS20Packet - pointer to the delete message itself
//
// RETURNS: NONE
//
void  S20DeleteMsg
(
    PS20DELETEPACKET  pS20Packet
)
{
    DebugEntry(S20DeleteMsg);

    TRACE_OUT(("S20_DELETE from [%d], for [%d], correlator %x",
        pS20Packet->header.user, pS20Packet->target, pS20Packet->correlator));

    //
    // ONLY SHARE CREATOR can delete people from share
    //

    if (!g_s20ShareCorrelator)
    {
        WARNING_OUT(("S20_DELETE, ignoring we're not in a share"));
        DC_QUIT;
    }

    if (pS20Packet->target != g_s20LocalID)
    {
        //
        // Not for us, ignore.
        //
        DC_QUIT;
    }

    if (g_s20ShareCorrelator != pS20Packet->correlator)
    {
        WARNING_OUT(("Received S20_DELETE from [%d] with unknown correlator %x",
            pS20Packet->header.user, pS20Packet->correlator));
        S20FlushSendOrQueueControlPacket(S20_LEAVE, pS20Packet->correlator,
            0, NET_TOP_PRIORITY);
        DC_QUIT;
    }

    if (S20_GET_CREATOR(g_s20ShareCorrelator) != pS20Packet->header.user)
    {
        WARNING_OUT(("Received S20_DELETE from [%d] who did not create share, ignore",
            pS20Packet->header.user));
        DC_QUIT;
    }

    //
    // Now handle incoming messages according to state.
    //
    switch (g_s20State)
    {
        case S20_SHARE_PEND:
        case S20_SHARE_STARTING:
            //
            // Just tell everyone else we're leaving and then issue a
            // SHARE_ENDED event.
            //
            TRACE_OUT(("CP LEAVE %lu %d", g_s20ShareCorrelator, 0));
            S20FlushSendOrQueueControlPacket(S20_LEAVE,
                                             g_s20ShareCorrelator,
                                             0,
                                             NET_TOP_PRIORITY);
            // FALL THROUGH

        case S20_IN_SHARE:
            SC_End();
            g_s20State = S20_NO_SHARE;
            TRACE_OUT(("g_s20State is S20_NO_SHARE"));
            break;

        default:
            break;
    }

DC_EXIT_POINT:
    DebugExitVOID(S20DeleteMsg);
}


//
// FUNCTION: S20LeaveMsg
//
// DESCRIPTION:
//
// Handles an incoming leave message.
//
// PARAMETERS:
//
// pS20Packet - pointer to the leave message itself
//
// RETURNS: NONE
//
void  S20LeaveMsg(PS20LEAVEPACKET  pS20Packet)
{
    DebugEntry(S20LeaveMsg);

    TRACE_OUT(("S20_LEAVE from [%d], correlator %x",
        pS20Packet->header.user, pS20Packet->correlator));

    if (!g_s20ShareCorrelator)
    {
        WARNING_OUT(("S20_LEAVE, ignoring we're not in a share"));
        DC_QUIT;
    }

    if (g_s20ShareCorrelator != pS20Packet->correlator)
    {
        WARNING_OUT(("Received S20_LEAVE from [%d] for unknown correlator %x",
            pS20Packet->header.user, pS20Packet->correlator));
        DC_QUIT;
    }

    switch (g_s20State)
    {
        case S20_IN_SHARE:
            //
            // We only need to handle this when we are in a share.
            //
            S20MaybeIssuePersonDelete(pS20Packet->header.user);
            break;

        default:
            break;
    }

DC_EXIT_POINT:
    DebugExitVOID(S20LeaveMsg);
}


//
// FUNCTION: S20EndMsg
//
// DESCRIPTION:
//
// Handles an incoming end message.
//
// PARAMETERS:
//
// pS20Packet - pointer to the end message itself
//
// RETURNS: NONE
//
void  S20EndMsg(PS20ENDPACKET  pS20Packet)
{
    DebugEntry(S20EndMsg);

    TRACE_OUT(("S20_END from [%d], correlator %x",
        pS20Packet->header.user, pS20Packet->correlator));

    if (!g_s20ShareCorrelator)
    {
        // We don't care
        WARNING_OUT(("S20_END ignored, not in share"));
        DC_QUIT;
    }

    if (g_s20ShareCorrelator != pS20Packet->correlator)
    {
        //
        // Just discard this.
        //
        WARNING_OUT(("Received S20_END from [%d] with unknown correlator %x",
            pS20Packet->header.user, pS20Packet->correlator));
        DC_QUIT;
    }

    //
    // Only the share creator can end the share.
    //
    if (S20_GET_CREATOR(g_s20ShareCorrelator) != pS20Packet->header.user)
    {
        WARNING_OUT(("Received S20_END from [%d] who did not create share, simply remove from user list.",
            pS20Packet->header.user));
        if (g_s20State == S20_IN_SHARE)
        {
            S20MaybeIssuePersonDelete(pS20Packet->header.user);
        }
        DC_QUIT;
    }

    switch (g_s20State)
    {
        case S20_IN_SHARE:
        case S20_SHARE_PEND:
        case S20_SHARE_STARTING:
            //
            // We just need to generate a share ended event.
            //
            SC_End();
            g_s20State = S20_NO_SHARE;
            TRACE_OUT(("g_s20State is S20_NO_SHARE"));
            break;

        default:
            break;
    }

DC_EXIT_POINT:
    DebugExitVOID(S20EndMsg);
}



//
// S20CollisionMsg()
//
// DESCRIPTION:
//
// Handles an incoming collision message.
//
// PARAMETERS:
//
// pS20Packet - pointer to the collision message itself
//
// RETURNS: NONE
//
void  S20CollisionMsg(PS20COLLISIONPACKET pS20Packet)
{
    DebugEntry(S20CollisionMsg);

    TRACE_OUT(("S20_COLLISION from [%d], correlator %x",
        pS20Packet->header.user, pS20Packet->correlator));

    if (!g_s20ShareCorrelator)
    {
        // We don't care
        WARNING_OUT(("S20_COLLISION ignored, not in share"));
        DC_QUIT;

    }

    if (g_s20ShareCorrelator != pS20Packet->correlator)
    {
        //
        // Just discard this.
        //
        WARNING_OUT(("Received S20_COLLISION from [%d] with unknown correlator %x",
            pS20Packet->header.user, pS20Packet->correlator));
        DC_QUIT;
    }

    //
    // If we created our own share, but got a collision from the remote,
    // then kill our share.
    //
    if (S20_GET_CREATOR(g_s20ShareCorrelator) != g_s20LocalID)
    {
        TRACE_OUT(("S20_COLLISION ignored, we didn't create share"));
        DC_QUIT;
    }

    switch (g_s20State)
    {
        case S20_IN_SHARE:
        case S20_SHARE_PEND:
        case S20_SHARE_STARTING:
            //
            // We just need to generate a share ended event.
            //
            SC_End();
            g_s20State = S20_NO_SHARE;
            TRACE_OUT(("g_s20State is S20_NO_SHARE"));
            break;

        default:
            break;
    }

DC_EXIT_POINT:
    DebugExitVOID(S20CollisionMsg);
}


//
// FUNCTION: S20DataMsg
//
// DESCRIPTION:
//
// Handles an incoming data message.
//
// PARAMETERS:
//
// pS20Packet - pointer to the data message itself
//
// RETURNS: TRUE - free the event, FALSE - do not free the event
//
void S20DataMsg(PS20DATAPACKET  pS20Packet)
{
    DebugEntry(S20DataMsg);

    ASSERT(!IsBadWritePtr(pS20Packet, sizeof(S20DATAPACKET)));
    ASSERT(!IsBadWritePtr(pS20Packet, sizeof(S20DATAPACKET) - sizeof(DATAPACKETHEADER) +
        pS20Packet->dataLength));

    //
    // Check if we're interseted in this data.
    //
    if ((pS20Packet->correlator == g_s20ShareCorrelator) &&
        (g_s20State == S20_IN_SHARE) &&
        g_asSession.pShare)
    {
        //
        // Return it.
        //
        g_asSession.pShare->SC_ReceivedPacket(pS20Packet);
    }

    DebugExitVOID(S20DataMsg);
}


//
// FUNCTION: S20MaybeAddNewParty
//
// DESCRIPTION:
//
// If the specified party has not already been added then try to add them
// now.
//
// PARAMETERS:
//
// userID     - the new party's network user ID.
// lenCaps    - the length of the new party's capabilities.
// lenName    - the length of the new party's name.
// pData      - a pointer to the new party's data which contains the name
//               followed by the capabilities data.
//
// RETURNS:
// BOOL for success
//
BOOL  S20MaybeAddNewParty
(
    MCSID   mcsID,
    UINT    lenCaps,
    UINT    lenName,
    LPBYTE  pData
)
{
    BOOL    rc = FALSE;
    UINT    oldState;
    LPBYTE  pCaps        = NULL;
    BOOL    memAllocated = FALSE;

    DebugEntry(S20MaybeAddNewParty);

    //
    // If we don't have a share, fail.
    //
    if (!g_asSession.pShare)
    {
        WARNING_OUT(("No ASShare; ignoring add party for [%d]", mcsID));
        DC_QUIT;
    }

    //
    // Check if this party has already been added.
    //
    if (g_asSession.pShare->SC_ValidateNetID(mcsID, NULL))
    {
        TRACE_OUT(("S20MaybeAddNewParty: already added %u", mcsID));
        rc = TRUE;
        DC_QUIT;
    }

    //
    // We need the caps structure to be 4-byte aligned.  It currently
    // follows a variable-length name string and may therefore not be
    // aligned.  If it is not aligned, we allocate an aligned buffer and
    // copy it there.
    //
    if (0 != (((UINT_PTR)pData + lenName) % 4))
    {
        TRACE_OUT(("Capabilities data is unaligned - realigning"));

        //
        // Get a 4-byte aligned buffer for the capabilities data.
        //
        pCaps = new BYTE[lenCaps];
        if (!pCaps)
        {
            ERROR_OUT(("Could not allocate %u bytes for aligned caps.",
                     lenCaps));
            DC_QUIT;
        }

        //
        // Flag so we know to free the memory later.
        //
        memAllocated = TRUE;

        //
        // Copy the caps data into our 4-byte aligned memory block.
        //
        memcpy(pCaps, (pData + lenName), lenCaps);
    }
    else
    {
        //
        // The capabilities data is already aligned so we don't need to
        // move it.
        //
        pCaps = pData + lenName;
    }

    //
    // Make sure we are in a share before we issue person add events.
    //
    oldState = g_s20State;
    g_s20State = S20_IN_SHARE;
    TRACE_OUT(("g_s20State is S20_IN_SHARE"));

    //
    // Attempt to add the new party.
    //
    rc = g_asSession.pShare->SC_PartyAdded(mcsID, (LPSTR)pData, lenCaps, pCaps);
    if (rc)
    {
        //
        // The new party has been accepted so send a response packet.  Do
        // this at ALL priorities, so it gets there before any type of data
        // at one particular priority.
        //
        TRACE_OUT(("CP RESPOND %lu %d", g_s20ShareCorrelator, 0));
        S20FlushSendOrQueueControlPacket(S20_RESPOND, g_s20ShareCorrelator,
                mcsID, NET_TOP_PRIORITY | NET_SEND_ALL_PRIORITIES);
    }
    else
    {
        g_asSession.pShare->SC_PartyDeleted(mcsID);

        //
        // Reset the state back to what it was if we failed.
        //
        g_s20State = oldState;
        TRACE_OUT(("g_s20State is %u", g_s20State));

        if (S20_GET_CREATOR(g_s20ShareCorrelator) == g_s20LocalID)
        {
             //
             // The new party has been rejected so send a delete packet.
             //
             TRACE_OUT(("CP DELETE %lu %u", g_s20ShareCorrelator, mcsID));
             S20FlushSendOrQueueControlPacket(S20_DELETE, g_s20ShareCorrelator,
                    mcsID, NET_TOP_PRIORITY);
        }
    }

DC_EXIT_POINT:
    //
    // Free memory used to store aligned caps.
    //
    if (memAllocated)
    {
        delete[] pCaps;
    }

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


//
// FUNCTION: S20NewCorrelator
//
// DESCRIPTION:
//
// Returns a new correlator for us to use when we are creating a share.
// This is a combination of our mcsID (low 16 bits in Intel format) and
// a generation count (high 16 bits in Intel format).
//
// PARAMETERS: NONE
//
// RETURNS: the new correlator
//
//
UINT  S20NewCorrelator(void)
{
    UINT    correlator;

    DebugEntry(S20NewCorrelator);

    g_s20Generation++;

    correlator = g_s20LocalID | (((UINT)(g_s20Generation & 0xFFFF)) << 16);

    DebugExitDWORD(S20NewCorrelator, correlator);
    return(correlator);
}




//
// FUNCTION: S20MaybeIssuePersonDelete
//
// DESCRIPTION:
//
// If the supplied person is in the share then issue a PARTY_DELETED event
// for them.
//
// PARAMTERS:
//
// mcsID - a network personID
//
// reason - the reason code to use
//
// RETURNS: NONE
//
//
void  S20MaybeIssuePersonDelete(MCSID mcsID)
{
    DebugEntry(S20MaybeIssuePersonDelete);

    if (g_asSession.pShare)
    {
        g_asSession.pShare->SC_PartyDeleted(mcsID);
    }

    //
    // HET will kill the share if there aren't any hosts left.  So we
    // don't need to do anything here.
    //

    DebugExitVOID(S20MaybeIssuePersonDelete);
}

//
// FUNCTION: S20StreamToS20Priority
//
// DESCRIPTION:
//
// Converts a stream ID into a NET priority
//
// PARAMETERS:
//
// streamID - the stream ID.
//
// RETURNS: the priority
//
//
const NET_PRIORITY c_StreamS20Priority[NUM_PROT_STR - 1] =
{
    NET_LOW_PRIORITY,       // PROT_STR_UPDATES
    NET_MEDIUM_PRIORITY,    // PROT_STR_MISC
    NET_MEDIUM_PRIORITY,    // PROT_STR_UNUSED
    NET_MEDIUM_PRIORITY,    // PROT_STR_INPUT
};

NET_PRIORITY S20StreamToS20Priority(UINT  streamID)
{
    NET_PRIORITY priority;

    DebugEntry(S20StreamToS20Priority);

    ASSERT(streamID > PROT_STR_INVALID);
    ASSERT(streamID < NUM_PROT_STR);
    ASSERT(streamID != PROT_STR_UNUSED);

    priority = c_StreamS20Priority[streamID - 1];

    DebugExitDWORD(S20StreamToS20Priority, priority);
    return(priority);
}




