/*++ Copyright (c) 1996 Microsoft Corporation Module Name: Protocol.c Abstract: Another Reliable Protocol (on DirectPlay) Author: Aaron Ogus (aarono) Environment: Win32 Revision History: Date Author Description ====== ====== ============================================================ 12/10/96 aarono Original 05/11/97 aarono convert from C++ COM object to 'C' library 2/03/98 aarono fixed ProtocolGetCaps for RAW 2/18/98 aarono changed InitProtocol to work later in connect process added new API handlers SendEx, GetMessageQueue, stub Cancel 2/18/98 aarono added Cancel support 2/19/98 aarono don't hook Shutdown anymore, dplay calls us explicitly on DP_OPEN (InitProtocol) DP_CLOSE (FiniProtocol) 2/20/98 aarono B#18827 not pulling cancelled sends from Q properly 3/5/98 aarono B#18962 allow non-reliable enumsessions reply when using protocol this avoids a bug where a remote on an invalid IPX net enums us and we get bogged down with RIPing in the response path. Actually hangs the machine and sometimes crashes IPX. 6/6/98 aarono Turn on throttling and windowing Notes: All direct calls from DPLAY to the PROTOCOL occur in this file. --*/ #include #include #include #include #include #include "mydebug.h" #include "handles.h" #include "arpd.h" #include "arpdint.h" #include "macros.h" #include "mytimer.h" /* Protocol Object Life: ===================== The protocol object is allocated on the DPLAY interface immediately after the call to SPInit. The protocol block is allocated and tacked onto the DPLAY interface. If the object is not allocated, the protocol pointer will be NULL. When the SP shutdown handler is called, the protocol object is released, first making sure that all other structures off of the protocol have been freed and all memory pools have been freed. SESSION Life: ============= Sessions are the structures that support the connection of a pair of PLAYERS. For each target playerid there is a SESSION structure. SESSIONS are accessed by converting playerids into indices into a session array, valid sessions are filled in, invalid or not yet seen ones are NULL. A session is allocated for every call to the SP CreatePlayer routine. When a DeletePlayer is received, the session is freed. There are races to create players and delete players so the session state is tracked. If the session is not in the OPEN state, mesages for the session are ABORTED/IGNORED(?). When the player is being removed, there may be stragling receives, these are rejected. Any packet received for a non-existent session is dropped. When a session is being closed, all pending sends are first completed. SEND Life: ========== STATISTICS Life: ================ RECEIVE Life: ============= How we hook in: =============== Receive: -------- HandlePacket in the ISP table has been replaced by the protocol's ProtocolHandlePacket routine. Each call to HandlePacket comes along with a pISP, from which we derive the pProtocol. If no pProtocol exits on the object, then we just call the old HandlePacket routine, otherwise we examine the packet and do our thing depending on what type of message it is and/or negotiated session parameters. Send/CreatePlayer/DeletePlayer/Shutdown: ---------------------------------------- If we install: We replace the interface pointers to these SP callbacks with our own and remember the existing ones. When we are called we do our processing and then call the handler in the SP. In the case of Send, we may not even call because we need to packetize the message. We also replace the packet size information in the SPData structure so that directplay's packetize and send code won't try to break up messages before we get them. System messages that we don't handle hopefully don't exceed the actual maximum frame size, else they will fail on a non-reliable transport. */ #ifdef DEBUG extern VOID My_GlobalAllocInit(); extern VOID My_GlobalAllocDeInit(); #endif // // Global pools should only be inited once, this counts opens. // No lock req'd since calls to spinit serialized in DirectPlay itself. // UINT nInitCount = 0; /*============================================================================= InitProtocol - initialize the protocol block and hook into the send path. Description: After each SP is initialized (in SPInit) this routine is called to hook the SP callbacks for the protocol. Also the protocol information for this instance of the protocol is allocated and initialized. Parameters: LPSPINITDATA pInitData - initialization block that was passed to the SP. We use it to hook in. Return Values: DP_OK - successfully hooked in. pProtocol on DIRECTPLAY object points to protocol obj. DPERR_GENERIC - didn't hook in. Also pProtocol in the DIRECTPLAY object will be NULL. -----------------------------------------------------------------------------*/ HRESULT WINAPI InitProtocol(DPLAYI_DPLAY *lpDPlay) { PPROTOCOL pProtocol; HRESULT hr; #define TABLE_INIT_SIZE 16 #define TABLE_GROW_SIZE 16 #ifdef DEBUG My_GlobalAllocInit(); #endif // Allocate the protocol block; pProtocol=My_GlobalAlloc(GMEM_FIXED|GMEM_ZEROINIT,sizeof(PROTOCOL)); if(!pProtocol){ hr=DPERR_NOMEMORY; goto exit; } // // Initialize protocol variables. // pProtocol->m_lpDPlay=lpDPlay; pProtocol->m_lpISP=lpDPlay->pISP; pProtocol->m_dwSPHeaderSize=lpDPlay->dwSPHeaderSize; pProtocol->m_nSendThreads=0; // we support any number of send threads! pProtocol->m_eState=Initializing; // we are initing InitializeCriticalSection(&pProtocol->m_ObjLock); InitializeCriticalSection(&pProtocol->m_SPLock); // Session lookup by ID list. InitializeCriticalSection(&pProtocol->m_SessionLock); pProtocol->m_SessionListSize=0; pProtocol->m_pSessions=NULL; // GLOBAL SENDQ InitializeCriticalSection(&pProtocol->m_SendQLock); InitBilink(&pProtocol->m_GSendQ); // // Get Multi-Media Timer Information. // if( timeGetDevCaps(&pProtocol->m_timecaps,sizeof(TIMECAPS)) != TIMERR_NOERROR ){ // make em up ASSERT(0); pProtocol->m_timecaps.wPeriodMin=5; pProtocol->m_timecaps.wPeriodMax=10000000; } // Send Thread Triggers - waits for Sends, DataGram IDs or Reliable IDs. pProtocol->m_hSendEvent=CreateEventA(NULL, FALSE, FALSE, NULL); if(!pProtocol->m_hSendEvent){ ASSERT(0); //TRACE all paths. hr=DPERR_NOMEMORY; goto exit1; } // Various descriptor pools. // These can't fail. if(!nInitCount){ InitializeCriticalSection(&g_SendTimeoutListLock); InitBilink(&g_BilinkSendTimeoutList); // only allocated once per process. InitSendDescs(); InitSendStats(); InitFrameBuffers(); InitBufferManager(); InitBufferPool(); } InitRcvDescs(pProtocol); nInitCount++; // // Get the datagram frame size from the SP // { DPCAPS Caps; DPSP_GETCAPSDATA GetCapsData; memset(&Caps,0,sizeof(DPCAPS)); Caps.dwMaxBufferSize = 0; Caps.dwSize = sizeof(DPCAPS); GetCapsData.dwFlags = 0; GetCapsData.lpCaps = &Caps; GetCapsData.idPlayer = 0; GetCapsData.lpISP = lpDPlay->pISP; CALLSP(lpDPlay->pcbSPCallbacks->GetCaps, &GetCapsData); pProtocol->m_dwSPMaxFrame=GetCapsData.lpCaps->dwMaxBufferSize; if(pProtocol->m_dwSPMaxFrame > 1400){ // Necessary since UDP reports huge capacity even though no receiver can // successfully receive a datagram of that size without throttle. pProtocol->m_dwSPMaxFrame = 1400; } GetCapsData.dwFlags = DPCAPS_GUARANTEED; hr=CALLSP(lpDPlay->pcbSPCallbacks->GetCaps, &GetCapsData); if(hr==DP_OK){ pProtocol->m_dwSPMaxGuaranteed=GetCapsData.lpCaps->dwMaxBufferSize; } if(!pProtocol->m_dwSPMaxGuaranteed){ pProtocol->m_dwSPMaxGuaranteed=pProtocol->m_dwSPMaxFrame; } } Lock(&pProtocol->m_ObjLock); // // Spin up the send thread // pProtocol->m_nSendThreads++; // Need for serialization starts here... pProtocol->m_hSendThread[0]=CreateThread( NULL, 4000, SendThread, (LPVOID)pProtocol, 0, &pProtocol->m_dwSendThreadId[0]); if(!pProtocol->m_hSendThread[0]){ ASSERT(0); //TRACE all paths. hr=DPERR_NOMEMORY; goto exit4; } pProtocol->lpHandleTable=InitHandleTable(TABLE_INIT_SIZE,&pProtocol->csHandleTable,TABLE_GROW_SIZE); if(!pProtocol->lpHandleTable){ goto exit5; } pProtocol->m_eState=Running; Unlock(&pProtocol->m_ObjLock); hr=DP_OK; exit: if(hr==DP_OK){ lpDPlay->pProtocol=(LPPROTOCOL_PART)pProtocol; } else { lpDPlay->pProtocol=NULL; } return hr; //exit6: if more init written, may need this. // FiniHandleTable(pProtocol->lpHandleTable, &pProtocol->csHandleTable); exit5: pProtocol->m_eState=ShuttingDown; SetEvent(pProtocol->m_hSendEvent); Unlock(&pProtocol->m_ObjLock); while(pProtocol->m_nSendThreads){ // wait for the send thread to shut off. Sleep(0); } CloseHandle(pProtocol->m_hSendThread[0]); Lock(&pProtocol->m_ObjLock); exit4: Unlock(&pProtocol->m_ObjLock); //exit3: FiniRcvDescs(pProtocol); nInitCount--; if(!nInitCount){ DeleteCriticalSection(&g_SendTimeoutListLock); FiniBufferPool(); FiniBufferManager(); FiniFrameBuffers(); FiniSendStats(); FiniSendDescs(); } //exit2: CloseHandle(pProtocol->m_hSendEvent); exit1: DeleteCriticalSection(&pProtocol->m_SPLock); DeleteCriticalSection(&pProtocol->m_ObjLock); DeleteCriticalSection(&pProtocol->m_SessionLock); DeleteCriticalSection(&pProtocol->m_SendQLock); My_GlobalFree(pProtocol); goto exit; #undef TABLE_INIT_SIZE #undef TABLE_GROW_SIZE } /*============================================================================= FiniProtocol - Description: Parameters: Return Values: -----------------------------------------------------------------------------*/ VOID WINAPI FiniProtocol(PPROTOCOL pProtocol) { DWORD tmKill; // // Kill the send thread. // DPF(1,"==>ProtShutdown\n"); Lock(&pProtocol->m_ObjLock); pProtocol->m_eState=ShuttingDown; SetEvent(pProtocol->m_hSendEvent); while(pProtocol->m_nSendThreads){ // wait for the send thread to shut off. Unlock(&pProtocol->m_ObjLock); Sleep(0); Lock(&pProtocol->m_ObjLock); } Unlock(&pProtocol->m_ObjLock); CloseHandle(pProtocol->m_hSendThread[0]); DPF(1,"SHUTDOWN: Protocol Send Thread ShutDown, waiting for sessions\n"); tmKill=timeGetTime()+60000; Lock(&pProtocol->m_SessionLock); while(pProtocol->m_nSessions && (((INT)(tmKill-timeGetTime())) > 0)){ Unlock(&pProtocol->m_SessionLock); //BUGBUG: race. when m_nSessions dereffed, there // is a race for the protocol to be freed. Sleep(0); Lock(&pProtocol->m_SessionLock); } DPF(1,"SHUTDOWN: Sessions All Gone Freeing other objects.\n"); // // Free the SESSION table // if(pProtocol->m_pSessions){ My_GlobalFree(pProtocol->m_pSessions); pProtocol->m_pSessions=0; } Unlock(&pProtocol->m_SessionLock); DeleteCriticalSection(&pProtocol->m_SendQLock); DeleteCriticalSection(&pProtocol->m_SessionLock); DeleteCriticalSection(&pProtocol->m_SPLock); DeleteCriticalSection(&pProtocol->m_ObjLock); CloseHandle(pProtocol->m_hSendEvent); FiniRcvDescs(pProtocol); nInitCount--; if(!nInitCount){ // Last one out, turn off the lights... DeleteCriticalSection(&g_SendTimeoutListLock); FiniBufferPool(); FiniBufferManager(); FiniFrameBuffers(); FiniSendStats(); FiniSendDescs(); } FiniHandleTable(pProtocol->lpHandleTable, &pProtocol->csHandleTable); My_GlobalFree(pProtocol); #ifdef DEBUG My_GlobalAllocDeInit(); #endif } /*============================================================================= ProtocolCreatePlayer - Called by DPlay when SP needs to be notified of new player creation. Description: Creates a session for the id. BUGBUG: if local, don't need this? Also notifies the SP. Parameters: Return Values: -----------------------------------------------------------------------------*/ HRESULT WINAPI ProtocolCreatePlayer(LPDPSP_CREATEPLAYERDATA pCreatePlayerData) { DPLAYI_DPLAY *lpDPlay; PPROTOCOL pProtocol; HRESULT hr=DP_OK; lpDPlay=((DPLAYI_DPLAY_INT *)pCreatePlayerData->lpISP)->lpDPlay; ASSERT(lpDPlay); pProtocol=(PPROTOCOL)lpDPlay->pProtocol; ASSERT(pProtocol); pProtocol->m_dwIDKey=(DWORD)lpDPlay->lpsdDesc->dwReserved1; // Creates the session and gets one refcount. hr=CreateNewSession(pProtocol, pCreatePlayerData->idPlayer); if(hr==DP_OK){ // Chain the call to the real provider. Lock(&pProtocol->m_SPLock); if(lpDPlay->pcbSPCallbacks->CreatePlayer){ hr=CALLSP(lpDPlay->pcbSPCallbacks->CreatePlayer,pCreatePlayerData); } Unlock(&pProtocol->m_SPLock); if(hr!=DP_OK){ PSESSION pSession; pSession=GetSession(pProtocol,pCreatePlayerData->idPlayer); //adds a ref if(pSession){ DecSessionRef(pSession); // unGetSession DecSessionRef(pSession); // blow it away, noone could access yet. } } } return hr; } /*============================================================================= ProtocolPreNotifyDeletePlayer Called to tell us a DELETEPLAYER message was enqueued for a particular player. We need to drop the player NOW! We don't notify the SP, that will happen when we are called in ProtocolDeletePlayer later when the pending queue is processed. Description: Dereference the session for the player. Parameters: Return Values: -----------------------------------------------------------------------------*/ HRESULT WINAPI ProtocolPreNotifyDeletePlayer(LPDPLAYI_DPLAY this, DPID idPlayer) { PPROTOCOL pProtocol; PSESSION pSession; HRESULT hr=DP_OK; pProtocol=(PPROTOCOL)this->pProtocol; ASSERT(pProtocol); pSession=GetSession(pProtocol,idPlayer); DPF(9,"==>Protocol Prenotify Delete Player %x, pSession %x\n",idPlayer, pSession); if(pSession){ pSession->hClosingEvent=0; #if 0 //BUGBUG: if you even think about putting this back, also do it in ProtocolDeletePlayer hClosingEvent=pSession->hClosingEvent=CreateEventA(NULL,FALSE,FALSE,NULL); if(hClosingEvent){ ResetEvent(hClosingEvent); } #endif Lock(&pProtocol->m_SendQLock); Lock(&pSession->SessionLock); switch(pSession->eState) { case Open: TimeOutSession(pSession); Unlock(&pSession->SessionLock); Unlock(&pProtocol->m_SendQLock); DecSessionRef(pSession); // balance GetSession DecSessionRef(pSession); // balance Creation - may destroy session, and signal event break; case Closing: case Closed: Unlock(&pSession->SessionLock); Unlock(&pProtocol->m_SendQLock); DecSessionRef(pSession); // balance GetSession break; } #if 0 if(hClosingEvent){ // Wait(hClosingEvent); CloseHandle(hClosingEvent); } else { DPF(0,"ProtocolPreNotifyDeletePlayer: couldn't get close event handle--not waiting...\n"); ASSERT(0); } #endif } else { DPF(0,"ProtocolPreNotifyDeletePlayer: couldn't find session for playerid %x\n",idPlayer); ASSERT(0); } DPF(9,"<==Protocol Prenotify DeletePlayer, hr=%x\n",hr); return hr; } /*============================================================================= ProtocolDeletePlayer - Called by DPlay when SP needs to be notified of player deletion. Description: Dereference the session for the player. Then notifies the SP. Parameters: Return Values: -----------------------------------------------------------------------------*/ HRESULT WINAPI ProtocolDeletePlayer(LPDPSP_DELETEPLAYERDATA pDeletePlayerData) { DPLAYI_DPLAY *lpDPlay; PPROTOCOL pProtocol; PSESSION pSession; HRESULT hr=DP_OK; //HANDLE hClosingEvent; lpDPlay=((DPLAYI_DPLAY_INT *)pDeletePlayerData->lpISP)->lpDPlay; ASSERT(lpDPlay); pProtocol=(PPROTOCOL)lpDPlay->pProtocol; ASSERT(pProtocol); pSession=GetSession(pProtocol,pDeletePlayerData->idPlayer); DPF(9,"==>Protocol Delete Player %x, pSession %x\n",pDeletePlayerData->idPlayer, pSession); if(pSession){ pSession->hClosingEvent=0; #if 0 //BUGBUG: if you even think about putting this back, also do it in ProtocolPreNotifyDeletePlayer hClosingEvent=pSession->hClosingEvent=CreateEventA(NULL,FALSE,FALSE,NULL); if(hClosingEvent){ ResetEvent(hClosingEvent); } #endif Lock(&pProtocol->m_SendQLock); Lock(&pSession->SessionLock); switch(pSession->eState) { case Open: TimeOutSession(pSession); Unlock(&pSession->SessionLock); Unlock(&pProtocol->m_SendQLock); DecSessionRef(pSession); // balance GetSession DecSessionRef(pSession); // balance Creation - may destroy session, and signal event break; case Closing: case Closed: Unlock(&pSession->SessionLock); Unlock(&pProtocol->m_SendQLock); DecSessionRef(pSession); // balance GetSession break; } #if 0 if(hClosingEvent){ // Wait(hClosingEvent); CloseHandle(hClosingEvent); } else { DPF(0,"ProtocolDeletePlayer: couldn't get close event handle--not waiting...\n"); ASSERT(0); } #endif } else { DPF(0,"ProtocolDeletePlayer: couldn't find session for playerid %x, ok if ProtocolPreNotifyDeletPlayer ran.\n",pDeletePlayerData->idPlayer); } DPF(9,"Protocol, deleted player id %x\n",pDeletePlayerData->idPlayer); DPF(9,"<==ProtocolDeletePlayer, hr=%x\n",hr); return hr; } /*============================================================================= ProtocolSendEx - Description: Parameters: Return Values: -----------------------------------------------------------------------------*/ HRESULT WINAPI ProtocolSendEx(LPDPSP_SENDEXDATA pSendData) { DPSP_SENDDATA sd; DPLAYI_DPLAY *lpDPlay; PPROTOCOL pProtocol; HRESULT hr=DP_OK; DWORD dwCommand; PUCHAR pBuffer; lpDPlay=((DPLAYI_DPLAY_INT *)pSendData->lpISP)->lpDPlay; ASSERT(lpDPlay); pProtocol=(PPROTOCOL)lpDPlay->pProtocol; ASSERT(pProtocol); ASSERT(lpDPlay->dwFlags & DPLAYI_PROTOCOL); if(pSendData->lpSendBuffers->len >= 8){ pBuffer=pSendData->lpSendBuffers->pData; if((*((DWORD *)pBuffer)) == SIGNATURE('p','l','a','y')){ dwCommand=GET_MESSAGE_COMMAND((LPMSG_SYSMESSAGE)pBuffer); switch(dwCommand){ case DPSP_MSG_PACKET2_DATA: case DPSP_MSG_PACKET2_ACK: case DPSP_MSG_PACKET: goto send_non_protocol_message; break; default: break; } } } // BUGBUG, make Send take the SENDEXDATA struct only. hr=Send(pProtocol, pSendData->idPlayerFrom, pSendData->idPlayerTo, pSendData->dwFlags, pSendData->lpSendBuffers, pSendData->cBuffers, pSendData->dwPriority, pSendData->dwTimeout, pSendData->lpDPContext, pSendData->lpdwSPMsgID, TRUE, NULL); // forces us to be called back in InternalSendComplete, if Send is ASYNC. return hr; send_non_protocol_message: ENTER_DPLAY(); Lock(&pProtocol->m_SPLock); if(lpDPlay->pcbSPCallbacks->SendEx){ hr=CALLSP(lpDPlay->pcbSPCallbacks->SendEx,pSendData); } else { hr=ConvertSendExDataToSendData(lpDPlay, pSendData, &sd); if(hr==DP_OK){ hr=CALLSP(lpDPlay->pcbSPCallbacks->Send, &sd); MsgFree(NULL, sd.lpMessage); } } Unlock(&pProtocol->m_SPLock); LEAVE_DPLAY(); return hr; } /*============================================================================= ProtocolGetMessageQueue - Description: Parameters: Return Values: -----------------------------------------------------------------------------*/ HRESULT WINAPI ProtocolGetMessageQueue(LPDPSP_GETMESSAGEQUEUEDATA pGetMessageQueueData) { #define pData pGetMessageQueueData DPLAYI_DPLAY *lpDPlay; PPROTOCOL pProtocol; PSESSION pSession; HRESULT hr=DP_OK; BILINK *pBilink; PSEND pSend; DWORD dwNumMsgs; DWORD dwNumBytes; lpDPlay=((DPLAYI_DPLAY_INT *)pData->lpISP)->lpDPlay; ASSERT(lpDPlay); pProtocol=(PPROTOCOL)lpDPlay->pProtocol; ASSERT(pProtocol); dwNumMsgs=0; dwNumBytes=0; if(!pData->idTo && !pData->idFrom){ // just wants totals, I know that! EnterCriticalSection(&pProtocol->m_SendQLock); dwNumMsgs = pProtocol->m_dwMessagesPending; dwNumBytes = pProtocol->m_dwBytesPending; LeaveCriticalSection(&pProtocol->m_SendQLock); } else if(pData->idTo){ // Given idTo, walk that target's sendQ pSession=GetSysSession(pProtocol,pData->idTo); if(!pSession) { DPF(0,"GetMessageQueue: NO SESSION for idTo %x, returning INVALIDPLAYER\n",pData->idTo); hr=DPERR_INVALIDPLAYER; goto exit; } EnterCriticalSection(&pSession->SessionLock); pBilink=pSession->SendQ.next; while(pBilink != &pSession->SendQ){ pSend=CONTAINING_RECORD(pBilink, SEND, SendQ); pBilink=pBilink->next; if((pSend->idTo==pData->idTo) && (!pData->idFrom || (pSend->idFrom == pData->idFrom))){ dwNumBytes += pSend->MessageSize; dwNumMsgs += 1; } } LeaveCriticalSection(&pSession->SessionLock); DecSessionRef(pSession); } else { ASSERT(pData->idFrom); // Geting Queue for a from id, this is most costly EnterCriticalSection(&pProtocol->m_SendQLock); pBilink=pProtocol->m_GSendQ.next; while(pBilink != &pProtocol->m_GSendQ){ pSend=CONTAINING_RECORD(pBilink, SEND, m_GSendQ); pBilink=pBilink->next; if(pData->idFrom == pSend->idFrom){ if(!pData->idTo || pData->idTo==pSend->idTo){ dwNumBytes += pSend->MessageSize; dwNumMsgs += 1; } } } LeaveCriticalSection(&pProtocol->m_SendQLock); } if(pData->lpdwNumMsgs){ *pData->lpdwNumMsgs=dwNumMsgs; } if(pData->lpdwNumBytes){ *pData->lpdwNumBytes=dwNumBytes; } exit: return hr; #undef pData } /*============================================================================= ProtocolCancel - Description: Parameters: Return Values: -----------------------------------------------------------------------------*/ HRESULT WINAPI ProtocolCancel(LPDPSP_CANCELDATA pCancelData) { #define pData pCancelData DPLAYI_DPLAY *lpDPlay; PPROTOCOL pProtocol; HRESULT hr=DP_OK; DWORD nCancelled=0; BILINK *pBilink; BOOL bCancel; UINT i; UINT j; DWORD dwContext; PSEND pSend; lpDPlay=((DPLAYI_DPLAY_INT *)pData->lpISP)->lpDPlay; ASSERT(lpDPlay); pProtocol=(PPROTOCOL)lpDPlay->pProtocol; ASSERT(pProtocol); EnterCriticalSection(&pProtocol->m_SendQLock); if(pData->dwFlags) { // either cancelpriority or cancel all, either way we // need to scan... pBilink=pProtocol->m_GSendQ.next; while(pBilink!=&pProtocol->m_GSendQ){ pSend=CONTAINING_RECORD(pBilink, SEND, m_GSendQ); pBilink=pBilink->next; bCancel=FALSE; Lock(&pSend->SendLock); switch(pSend->SendState){ case Start: case WaitingForId: if(pData->dwFlags & DPCANCELSEND_PRIORITY) { // Cancel sends in priority range. if((pSend->Priority <= pData->dwMaxPriority) && (pSend->Priority >= pData->dwMinPriority)){ bCancel=TRUE; } } else if(pData->dwFlags & DPCANCELSEND_ALL) { // Cancel all sends that can be. bCancel=TRUE; } else { ASSERT(0); // Invalid flags, should never happen } if(bCancel){ if(pSend->SendState == WaitingForId){ if(pSend->dwFlags & DPSEND_GUARANTEED){ InterlockedDecrement(&pSend->pSession->nWaitingForMessageid); } else { InterlockedDecrement(&pSend->pSession->nWaitingForDGMessageid); } } nCancelled+=1; pSend->SendState=Cancelled; } break; default: DPF(5,"Couldn't cancel send %x in State %d, already sending...\n",pSend,pSend->SendState); } Unlock(&pSend->SendLock); } } else { // No flags, therefore we have a list to cancel so lookup // each send and cancel rather than scanning as above. // Run through the list, find the sends and lock em 1st, if we find one that doesn't lookup, // or one not in the start state, then we bail. We then unlock them all. for(i=0;icSPMsgID;i++){ dwContext=(DWORD)((DWORD_PTR)((*pData->lprglpvSPMsgID)[i])); pSend=(PSEND)ReadHandleTableEntry(&pProtocol->lpHandleTable, &pProtocol->csHandleTable, dwContext); if(pSend){ Lock(&pSend->SendLock); if(pSend->SendState != Start && pSend->SendState != WaitingForId){ Unlock(&pSend->SendLock); hr=DPERR_CANCELFAILED; break; } } else { hr=DPERR_CANCELFAILED; break; } } if(hr==DPERR_CANCELFAILED) { // release all the locks. for(j=0;jlprglpvSPMsgID)[j])); pSend=(PSEND)ReadHandleTableEntry(&pProtocol->lpHandleTable, &pProtocol->csHandleTable, dwContext); ASSERT(pSend); Unlock(&pSend->SendLock); } } else { // mark the sends cancelled and release all the locks. for(i=0;icSPMsgID;i++){ dwContext=(DWORD)((DWORD_PTR)((*pData->lprglpvSPMsgID)[i])); pSend=(PSEND)ReadHandleTableEntry(&pProtocol->lpHandleTable, &pProtocol->csHandleTable, dwContext); ASSERT(pSend); if(pSend->SendState == WaitingForId){ if(pSend->dwFlags & DPSEND_GUARANTEED){ InterlockedDecrement(&pSend->pSession->nWaitingForMessageid); } else { InterlockedDecrement(&pSend->pSession->nWaitingForDGMessageid); } } pSend->SendState=Cancelled; nCancelled+=1; Unlock(&pSend->SendLock); } } } LeaveCriticalSection(&pProtocol->m_SendQLock); SetEvent(pProtocol->m_hSendEvent); return hr; #undef pData } /*============================================================================= ProtocolSend - Send A message synchronously. Description: Parameters: Return Values: -----------------------------------------------------------------------------*/ DWORD bForceDGAsync=FALSE; HRESULT WINAPI ProtocolSend(LPDPSP_SENDDATA pSendData) { DPLAYI_DPLAY *lpDPlay; PPROTOCOL pProtocol; HRESULT hr=DP_OK; DWORD dwCommand; DWORD dwPriority; DWORD dwFlags; PUCHAR pBuffer; MEMDESC memdesc; lpDPlay=((DPLAYI_DPLAY_INT *)pSendData->lpISP)->lpDPlay; ASSERT(lpDPlay); pProtocol=(PPROTOCOL)lpDPlay->pProtocol; ASSERT(pProtocol); pBuffer=&(((PUCHAR)(pSendData->lpMessage))[pProtocol->m_dwSPHeaderSize]); if((*((DWORD *)pBuffer)) == SIGNATURE('p','l','a','y')){ dwCommand=GET_MESSAGE_COMMAND((LPMSG_SYSMESSAGE)pBuffer); switch(dwCommand){ case DPSP_MSG_PACKET2_DATA: case DPSP_MSG_PACKET2_ACK: case DPSP_MSG_ENUMSESSIONSREPLY: case DPSP_MSG_PACKET: goto send_non_protocol_message; break; default: break; } } memdesc.pData=((PUCHAR)pSendData->lpMessage)+pProtocol->m_dwSPHeaderSize; memdesc.len =pSendData->dwMessageSize-pProtocol->m_dwSPHeaderSize; if(pSendData->dwFlags & DPSEND_HIGHPRIORITY){ pSendData->dwFlags &= ~(DPSEND_HIGHPRIORITY); dwPriority=0xFFFFFFFE; } else { dwPriority=1000; } dwFlags = pSendData->dwFlags; if(bForceDGAsync && !(dwFlags&DPSEND_GUARANTEE)){ // for testing old apps with protocol make datagram sends // async so that the application doesn't block. dwFlags |= DPSEND_ASYNC; } hr=Send(pProtocol, pSendData->idPlayerFrom, pSendData->idPlayerTo, dwFlags, &memdesc, 1, dwPriority, 0, NULL, NULL, FALSE, NULL); return hr; send_non_protocol_message: if((*((DWORD *)pBuffer)) == SIGNATURE('p','l','a','y')){ DPF(9,"Send Message %d Ver %d\n", pBuffer[4]+(pBuffer[5]<<8),pBuffer[6]+(pBuffer[7]<<8)); } ENTER_DPLAY(); Lock(&pProtocol->m_SPLock); hr=CALLSP(lpDPlay->pcbSPCallbacks->Send,pSendData); Unlock(&pProtocol->m_SPLock); LEAVE_DPLAY(); return hr; } /*============================================================================= GetPlayerLatency - Get Latency for a player Description: Parameters: Return Values: -----------------------------------------------------------------------------*/ DWORD GetPlayerLatency(LPDPLAYI_DPLAY lpDPlay, DPID idPlayer) { PPROTOCOL pProtocol; PSESSION pSession; DWORD dwLatency=0; // default, means I don't know latency pProtocol=(PPROTOCOL)lpDPlay->pProtocol; ASSERT(pProtocol); pSession=GetSession(pProtocol,idPlayer); DPF(9,"==>Protocol GetPlayer Latency %x, pSession %x\n",idPlayer, pSession); if(pSession){ Lock(&pSession->SessionLock); // Protocol Latency is round trip in 24.8 fixed point, // we net round trip latency divided by 2, so shift right 9. dwLatency=(pSession->FpLocalAverageLatency)>>(9); Unlock(&pSession->SessionLock); DecSessionRef(pSession); // balance GetSession } DPF(9,"<==Protocol GetPlayerLatency, returning dwLat=%x\n",dwLatency); return dwLatency; } /*============================================================================= ProtocolGetCaps - Get Service Provider Capabilities Description: Parameters: Return Values: -----------------------------------------------------------------------------*/ HRESULT WINAPI ProtocolGetCaps(LPDPSP_GETCAPSDATA pGetCapsData) { #define ALL_PROTOCOLCAPS (DPCAPS_SENDPRIORITYSUPPORTED | \ DPCAPS_ASYNCSUPPORTED | \ DPCAPS_SENDTIMEOUTSUPPORTED | \ DPCAPS_ASYNCCANCELSUPPORTED ) DPLAYI_DPLAY *lpDPlay; PPROTOCOL pProtocol; HRESULT hr=DP_OK; lpDPlay=((DPLAYI_DPLAY_INT *)pGetCapsData->lpISP)->lpDPlay; ASSERT(lpDPlay); pProtocol=(PPROTOCOL)lpDPlay->pProtocol; ASSERT(pProtocol); // Chain the call to the real provider. Lock(&pProtocol->m_SPLock); if(lpDPlay->pcbSPCallbacks->GetCaps){ hr=CALLSP(lpDPlay->pcbSPCallbacks->GetCaps,pGetCapsData); } Unlock(&pProtocol->m_SPLock); // if it fails, this doesn't hurt if(lpDPlay->dwFlags & DPLAYI_DPLAY_PROTOCOL) { // 1 megabyte is lots (says Jamie Osborne) pGetCapsData->lpCaps->dwMaxBufferSize=0x100000; pGetCapsData->lpCaps->dwFlags |= ALL_PROTOCOLCAPS; } if(pGetCapsData->idPlayer && !pGetCapsData->lpCaps->dwLatency){ // SP refused to guess at latency, so use ours. pGetCapsData->lpCaps->dwLatency=GetPlayerLatency(lpDPlay, pGetCapsData->idPlayer); } return hr; #undef ALL_PROTOCOLCAPS } DWORD ExtractProtocolIds(PUCHAR pInBuffer, PUINT pdwIdFrom, PUINT pdwIdTo) { PCHAR pBuffer=pInBuffer; DWORD dwIdFrom=0; DWORD dwIdTo=0; dwIdFrom=*pBuffer&0x7F; if(*pBuffer&0x80){ pBuffer++; dwIdFrom=dwIdFrom+((*pBuffer&0x7F)<<7); if(*pBuffer&0x80){ pBuffer++; dwIdFrom=dwIdFrom+((*pBuffer&0x7F)<<14); if(dwIdFrom > 0xFFFF || *pBuffer&0x80){ DPF(0,"INVALID FROM ID %x IN MESSAGE, REJECTING PACKET\n",dwIdFrom); return 0; } } } if(dwIdFrom==0xFFFF){ dwIdFrom=0x70; } pBuffer++; dwIdTo=*pBuffer&0x7F; if(*pBuffer&0x80){ pBuffer++; dwIdTo=dwIdTo+((*pBuffer&0x7F)<<7); if(*pBuffer&0x80){ pBuffer++; dwIdTo=dwIdTo+((*pBuffer&0x7F)<<14); if(dwIdTo > 0xFFFF || *pBuffer&0x80){ DPF(0,"INVALID TO ID %x IN MESSAGE, REJECTING PACKET\n",dwIdTo); return 0; } } } *pdwIdFrom=dwIdFrom; *pdwIdTo=dwIdTo; pBuffer++; // DPF(9, "In ExtractProtocolIds: from %x became %x\n", *(DWORD *)pInBuffer, dwIdFrom); return (DWORD)(pBuffer-pInBuffer); } /*============================================================================= DP_SP_ProtocolHandleMessage - Packet handler for Dplay protocol Description: All messages go through here when the protocol is active. If the message is not a protocol message, this routine doesn't process it and returns DPERR_NOTHANDLED to let other layers (probably PacketizeAndSend) process it. Parameters: IDirectPlaySP * pISP - pointer to pISP interface LPBYTE pReceiveBuffer - a single buffer of data DWORD dwMessageSize - length of the buffer LPVOID pvSPHeader - pointer to SP's header used in Reply Return Values: Notes: We don't worry about re-entering DP_SP_HandleMessage since we are calling only when a receive has completed and we are in the callback from the SP to directplay, so effectively the SP is serializing receives for us. The receive code is actually written to be re-entrant, so if we ever decide to allow concurrent receive processing the protocol can handle it. Protocol messages start with 'P','L','A','Y','0xFF' when not RAW. DPLAY gets handleMessage first, and hands off to protocol if active. -----------------------------------------------------------------------------*/ HRESULT DPAPI DP_SP_ProtocolHandleMessage( IDirectPlaySP * pISP, LPBYTE pReceiveBuffer, DWORD dwMessageSize, LPVOID pvSPHeader) { DPLAYI_DPLAY *lpDPlay; DWORD dwIdFrom, dwIdTo; PBUFFER pRcvBuffer; PPROTOCOL pProtocol; lpDPlay=DPLAY_FROM_INT(pISP); pProtocol=(PPROTOCOL)lpDPlay->pProtocol; if(pProtocol->m_lpDPlay->dwFlags & DPLAYI_DPLAY_PROTOCOL){ // Running in RAW mode there is no dplay header on protocol // messages. If we see one with a header or we don't receive // a message large enough to be a protocol message we punt it. if(dwMessageSize >= 4 && (*((DWORD *)pReceiveBuffer)) == SIGNATURE('p','l','a','y')) { // Got a system message. goto handle_non_protocol_message; } if( dwMessageSize < 6 ){ goto handle_non_protocol_message; } } else { // this can happen when shutting down. DPF(0,"Protocol still up, but no bits set, not handling receive (must be shutting down?)"); goto handle_non_protocol_message; } // Hey, this must be ours... Lock(&pProtocol->m_ObjLock); if(pProtocol->m_eState==Running){ // just a sanity check, we don't depend on it after dropping lock. DWORD idLen; Unlock(&pProtocol->m_ObjLock); idLen = ExtractProtocolIds(pReceiveBuffer,&dwIdFrom,&dwIdTo); if(!idLen){ goto handle_non_protocol_message; } pRcvBuffer=GetFrameBuffer(dwMessageSize-idLen); pRcvBuffer->len=dwMessageSize-idLen; memcpy(pRcvBuffer->pData, pReceiveBuffer+idLen,pRcvBuffer->len); DPF(9,"DP_SP_ProtocolHandleMessage From %x To %x\n",dwIdFrom,dwIdTo); ENTER_DPLAY(); ProtocolReceive((PPROTOCOL)lpDPlay->pProtocol, (WORD)dwIdFrom, (WORD)dwIdTo, pRcvBuffer,pvSPHeader); LEAVE_DPLAY(); } else { Unlock(&pProtocol->m_ObjLock); } return DP_OK; handle_non_protocol_message: return DPERR_NOTHANDLED; } // DP_SP_ProtocolSendComplete is the callback handler for all completions since there is no other // way to wrap the completion. When the protocol is not present, this just calls the DPLAY handler // immediately. VOID DPAPI DP_SP_ProtocolSendComplete( IDirectPlaySP * pISP, LPVOID lpvContext, HRESULT CompletionStatus) { DPLAYI_DPLAY *lpDPlay; PPROTOCOL pProtocol; lpDPlay=DPLAY_FROM_INT(pISP); if(lpDPlay->pProtocol){ // BUGBUG: when SP SendEx is used, we have to patch and xlate here. // for now, this should never happen. DEBUG_BREAK(); // Shouldn't get here yet. pProtocol=(PPROTOCOL)lpDPlay->pProtocol; DP_SP_SendComplete(pISP, lpvContext, CompletionStatus); } else { DP_SP_SendComplete(pISP, lpvContext, CompletionStatus); } }