/*---------------------------------------------------------------------------- * File: RTPRECV.C * Product: RTP/RTCP implementation * Description: Provides Receive Data Functionality. * * This listing is supplied under the terms * of a license agreement with Intel Corporation and * many not be copied nor disclosed except in accordance * with the terms of that agreement. * Copyright (c) 1995 Intel Corporation. *--------------------------------------------------------------------------*/ #include "rrcm.h" /*--------------------------------------------------------------------------- / Global Variables /--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------- / External Variables /--------------------------------------------------------------------------*/ extern PRTP_CONTEXT pRTPContext; extern RRCM_WS RRCMws; #if (defined(_DEBUG) || defined(PCS_COMPLIANCE)) //INTEROP extern LPInteropLogger RTPLogger; #endif /*---------------------------------------------------------------------------- * Function : RTPReceiveCheck * Description: Called when a packet is received. Handles any statistical * processing required for RTCP. * * Input : hRTPSession: handle returned by CreateRTPSession RTPsocket: socket on which the packet was received char *pPacket: pointer to packet buffer * cbTransferred: Number of bytes in packet * pFrom: sender address * fromlen: sender address length * * !!! IMPORTANT NOTE !!! * Currently assumes CSRC = 0 * !!! IMPORTANT NOTE !!! * * Return: Status indicating if the packet is OK or has a problem ---------------------------------------------------------------------------*/ DWORD RTPReceiveCheck ( HANDLE hRTPSession, SOCKET RTPsocket, char *pPacket, DWORD cbTransferred, PSOCKADDR pFrom, UINT fromlen ) { PRTP_SESSION pRTPSession = (PRTP_SESSION) hRTPSession; RTP_HDR_T *pRTPHeader = (RTP_HDR_T *)pPacket; PSSRC_ENTRY pSSRC = NULL; DWORD dwSSRC; DWORD oldSSRC; PSSRC_ENTRY pMySSRC; DWORD dwStatus = 0; struct sockaddr_in *pSSRCadr; IN_OUT_STR ("RTP : Enter RTPReceiveCheck()\n"); ASSERT (pRTPSession); // If Winsock error or runt packet(used to cancel recvs), signal completion to application // and do not repost. if (cbTransferred < RTP_HDR_MIN_LEN) { // don't report closeSocket() as an error when the application // have some pending buffers remaining // notify the user if an error occured, so he can free up // its receive resources. The byte count is set to 0 return RTP_RUNT_PACKET; } // Perform validity checking ASSERT (pRTPHeader); #if (defined(_DEBUG) || defined(PCS_COMPLIANCE)) if (RTPLogger) { //INTEROP InteropOutput (RTPLogger, (BYTE FAR*)(pRTPHeader), (int)cbTransferred, RTPLOG_RECEIVED_PDU | RTP_PDU); } #endif // Check RTP Headers for validity. If not valid, then repost buffers // to the network layer for a new receive. if (validateRTPHeader (pRTPHeader) ) { // Get pointer to SSRC entry table for this session // If SSRC in packet is > 1/2 MAX_RANGE of DWORD, start search from // tail of SSRC list, otherwise, start from front RRCMws.ntohl (RTPsocket, pRTPHeader->ssrc, &dwSSRC); if (dwSSRC > MAX_DWORD/2) { pSSRC = searchforSSRCatTail ( (PSSRC_ENTRY)pRTPSession->pRTCPSession->RcvSSRCList.prev, dwSSRC); } else { pSSRC = searchforSSRCatHead ( (PSSRC_ENTRY)pRTPSession->pRTCPSession->RcvSSRCList.next, dwSSRC); } // get my own SSRC used for this stream pMySSRC = searchForMySSRC ( (PSSRC_ENTRY)pRTPSession->pRTCPSession->XmtSSRCList.prev, RTPsocket); ASSERT (pMySSRC); // is this SSRC already known on the receive list ? if (pSSRC == NULL) { // don't create an entry for my own packet looping back on // a mcast group where loopback has not been turned off if (pMySSRC->SSRC != dwSSRC) { // new party heard from. Create an entry for it pSSRC = createSSRCEntry (dwSSRC, pRTPSession->pRTCPSession, pFrom, fromlen, FALSE); // notify application if interested RRCMnotification (RRCM_NEW_SOURCE_EVENT, pSSRC, dwSSRC, pRTPHeader->pt); } else { // my own SSRC received back // A collision occurs if the SSRC in the rcvd packet is // equal to mine, and the network transport address is // different from mine. // A loop occurs if after a collision has been resolved the // SSRC collides again from the same source transport address pSSRCadr = (PSOCKADDR_IN)&pMySSRC->from; if (((PSOCKADDR_IN)pFrom)->sin_addr.S_un.S_addr != pSSRCadr->sin_addr.S_un.S_addr) { // check if the source address is already in the // conflicting table. This identifes that somebody out // there is looping pckts back to me if (RRCMChkCollisionTable (pFrom, fromlen, pMySSRC)) { RRCM_DBG_MSG ("RTP : Loop Detected ...", 0, NULL, 0, DBG_NOTIFY); // loop already known dwStatus |= SSRC_LOOP_DETECTED; } else { RRCM_DBG_MSG ("RTP : Collision Detected ...", 0, NULL, 0, DBG_NOTIFY); // create new entry in conflicting address table RRCMAddEntryToCollisionTable (pFrom, fromlen, pMySSRC); // send RTCP BYE packet w/ old SSRC RTCPsendBYE (pMySSRC, "Loop/collision detected"); // select new SSRC oldSSRC = pMySSRC->SSRC; dwSSRC = getSSRC (pMySSRC->pRTCPses->RcvSSRCList, pMySSRC->pRTCPses->XmtSSRCList); EnterCriticalSection (&pMySSRC->critSect); pMySSRC->SSRC = dwSSRC; LeaveCriticalSection (&pMySSRC->critSect); // create new entry w/ old SSRC plus actual source // transport address in our receive list side, so the // packet actually en-route will be dealt with createSSRCEntry (oldSSRC, pRTPSession->pRTCPSession, pFrom, fromlen, FALSE); // notify application if interested RRCMnotification (RRCM_LOCAL_COLLISION_EVENT, pMySSRC, oldSSRC, 0); // loop already known dwStatus |= SSRC_COLLISION_DETECTED; } } else { // own packet looped back because the sender joined the // multicast group and loopback is not turned off dwStatus |= MCAST_LOOPBACK_NOT_OFF; } } } else if (pSSRC->dwSSRCStatus & THIRD_PARTY_COLLISION) { // this SSRC is marked as colliding. Reject the data dwStatus = THIRD_PARTY_COLLISION; } if (dwStatus == 0) { // do all the statistical updating stuff updateRTPStats (pRTPHeader, pSSRC, cbTransferred); // update the payload type for this SSRC pSSRC->PayLoadType = pRTPHeader->pt; } // SSRCList != NULL } // valid RTP Header else { dwStatus |= INVALID_RTP_HEADER; } IN_OUT_STR ("RTP : Exit RTPReceiveCallback()\n"); return dwStatus; } /*---------------------------------------------------------------------------- * Function : validateRTPHeader * Description: Performs basic checking of RTP Header (e.g., version number * and payload type range). * * Input : pRTPHeader: -> to an RTP header * * Return: TRUE, RTP Packet Header is valid * FALSE: Header is invalid ---------------------------------------------------------------------------*/ BOOL validateRTPHeader(RTP_HDR_T *pRTPHeader) { BOOL bStatus = TRUE; IN_OUT_STR ("RTP : Enter validateRTPHeader()\n"); if (! pRTPHeader) return FALSE; // Check version number is correct if (pRTPHeader->type != RTP_TYPE) bStatus = FALSE; // Next check that the Packet types look somewhat reasonable, // at least out of the RTCP range if (pRTPHeader->pt >= RTCP_SR) bStatus = FALSE; IN_OUT_STR ("RTP : Exit validateRTPHeader()\n"); return bStatus; } #if 0 /*---------------------------------------------------------------------------- * Function : RTPRecvFrom * Description: Intercepts receive requests from app. Handles any statistical * processing required for RTCP. Copies completion routine * from app and substitutes its own. Apps completion routine * will be called after RTP's completion routine gets called. * * Input : RTPsocket: RTP socket descriptor * pBuffers: -> to WSAbuf structure * dwBufferCount: Buffer count in WSAbuf structure * pNumBytesRecvd: -> to number of bytes received * pFlags: -> to flags * pFrom: -> to the source address * pFromLen: -> to source address length * pOverlapped: -> to overlapped I/O structure * pCompletionRoutine: -> to completion routine * * Return: RRCM_NoError = OK. * Otherwise(!=0) = Check RRCM.h file for references. ---------------------------------------------------------------------------*/ DWORD WINAPI RTPRecvFrom (SOCKET RTPsocket, LPWSABUF pBuffers, DWORD dwBufferCount, LPDWORD pNumBytesRecvd, LPDWORD pFlags, PSOCKADDR pFrom, LPINT pFromlen, LPWSAOVERLAPPED pOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE pCompletionRoutine) { int dwStatus = RRCM_NoError; int dwError; PRTP_SESSION pRTPSession; PRTP_BFR_LIST pRCVStruct; IN_OUT_STR ("RTP : Enter RTPRecvFrom()\n"); // If RTP context doesn't exist, report error and return. if (pRTPContext == NULL) { RRCM_DBG_MSG ("RTP : ERROR - No RTP Instance", 0, __FILE__, __LINE__, DBG_CRITICAL); IN_OUT_STR ("RTP : Exit RTPRecvFrom()\n"); return (MAKE_RRCM_ERROR(RRCMError_RTPInvalid)); } // Search for the proper session based on incoming socket pRTPSession = findSessionID(RTPsocket); if (pRTPSession == NULL) { RRCM_DBG_MSG ("RTP : ERROR - Invalid RTP session", 0, __FILE__, __LINE__, DBG_CRITICAL); IN_OUT_STR ("RTP : Exit RTPRecvFrom()\n"); return (MAKE_RRCM_ERROR(RRCMError_RTPInvalidSession)); } // We need to associate a completionRoutine's lpOverlapped with a // session. We look at each buffer and associate a socket so when // the completion routine is called, we can pull out the socket. if (dwStatus = saveRCVWinsockContext(pOverlapped, pBuffers, pCompletionRoutine, pRTPSession, dwBufferCount, pNumBytesRecvd, pFlags, pFrom, pFromlen, RTPsocket) != RRCM_NoError) { RRCM_DBG_MSG ("RTP : ERROR - Out of resources...", 0, __FILE__, __LINE__, DBG_NOTIFY); IN_OUT_STR ("RTP : Exit RTPRecvFrom()\n"); return (MAKE_RRCM_ERROR(dwStatus)); } // Forward to winsock, substituting our completion routine for the // one handed to us. dwStatus = RRCMws.recvFrom (RTPsocket, pBuffers, dwBufferCount, pNumBytesRecvd, pFlags, pFrom, pFromlen, pOverlapped, RTPReceiveCallback); // Check if Winsock Call succeeded if (dwStatus != 0) { // If serious error, the receive request won't proceed so // we must undo all our work dwError = GetLastError(); if ((dwError != WSA_IO_PENDING) && (dwError != WSAEMSGSIZE)) { // Reinstate the Apps WSAEVENT pRCVStruct = (PRTP_BFR_LIST)pOverlapped->hEvent; pOverlapped->hEvent = pRCVStruct->hEvent; RRCM_DBG_MSG ("RTP : ERROR - WSARecvFrom()", dwError, __FILE__, __LINE__, DBG_NOTIFY); // Return the struct to the free queue addToHeadOfList (&pRTPSession->pRTPFreeList, (PLINK_LIST)pRCVStruct, &pRTPSession->critSect); } } IN_OUT_STR ("RTP : Exit RTPRecvFrom()\n"); return (dwStatus); } /*---------------------------------------------------------------------------- * Function : RTPReceiveCallback * Description: Callback routine from Winsock2 Handles any statistical * processing required for RTCP. Copies completion routine * from app and substitutes its own. Apps completion routine * will be called after RTP's completion routine gets called. * * Input : dwError: I/O completion error code * cbTransferred: Number of bytes transferred * pOverlapped: -> to overlapped I/O structure * dwFlags: Flags * * !!! IMPORTANT NOTE !!! * Currently assumes CSRC = 0 * !!! IMPORTANT NOTE !!! * * Return: None ---------------------------------------------------------------------------*/ void CALLBACK RTPReceiveCallback (DWORD dwError, DWORD cbTransferred, LPWSAOVERLAPPED pOverlapped, DWORD dwFlags) { PRTP_SESSION pRTPSession; RTP_HDR_T *pRTPHeader; PRTP_BFR_LIST pRCVStruct; PSSRC_ENTRY pSSRC = NULL; DWORD dwSSRC; DWORD oldSSRC; PSSRC_ENTRY pMySSRC; DWORD dwRequeue = 0; struct sockaddr_in *pSSRCadr; IN_OUT_STR ("RTP : Enter RTPReceiveCallback()\n"); // GEORGEJ: catch Winsock 2 bug (94903) where I get a bogus callback // after WSARecv returns WSAEMSGSIZE. if (!dwError && ((int) cbTransferred < 0)) { RRCM_DBG_MSG ("RTP : RCV Callback : bad cbTransferred", cbTransferred, __FILE__, __LINE__, DBG_ERROR); return; } // The returning hEvent in the LPWSAOVERLAPPED struct contains the // information mapping the session and the buffer. pRCVStruct = (PRTP_BFR_LIST)pOverlapped->hEvent; // Search for the proper session based on incoming socket pRTPSession = (PRTP_SESSION)pRCVStruct->pSession; ASSERT (pRTPSession); // If Winsock error or runt packet(used to cancel recvs), signal completion to application // and do not repost. if (dwError || cbTransferred < RTP_HDR_MIN_LEN) { // don't report closeSocket() as an error when the application // have some pending buffers remaining if ((dwError != 65534) && (dwError == WSA_OPERATION_ABORTED)) { RRCM_DBG_MSG ("RTP : RCV Callback", dwError, __FILE__, __LINE__, DBG_ERROR); } // notify the user if an error occured, so he can free up // its receive resources. The byte count is set to 0 // Reinstate the AppSs WSAEVENT pOverlapped->hEvent = pRCVStruct->hEvent; // And call the apps completion routine pRCVStruct->pfnCompletionNotification (dwError, cbTransferred, pOverlapped, dwFlags); // Return the struct to the free queue addToHeadOfList (&pRTPSession->pRTPFreeList, (PLINK_LIST)pRCVStruct, &pRTPSession->critSect); IN_OUT_STR ("RTP : Exit RTPReceiveCallback()\n"); return; } // Perform validity checking pRTPHeader = (RTP_HDR_T *)pRCVStruct->pBuffer->buf; ASSERT (pRTPHeader); #if (defined(_DEBUG) || defined(PCS_COMPLIANCE)) if (RTPLogger) { //INTEROP InteropOutput (RTPLogger, (BYTE FAR*)(pRCVStruct->pBuffer->buf), (int)cbTransferred, RTPLOG_RECEIVED_PDU | RTP_PDU); } #endif // Check RTP Headers for validity. If not valid, then repost buffers // to the network layer for a new receive. if (validateRTPHeader (pRTPHeader) && (dwError == 0)) { // Get pointer to SSRC entry table for this session // If SSRC in packet is > 1/2 MAX_RANGE of DWORD, start search from // tail of SSRC list, otherwise, start from front RRCMws.ntohl (pRCVStruct->RTPsocket, pRTPHeader->ssrc, &dwSSRC); if (dwSSRC > MAX_DWORD/2) { pSSRC = searchforSSRCatTail ( (PSSRC_ENTRY)pRTPSession->pRTCPSession->RcvSSRCList.prev, dwSSRC); } else { pSSRC = searchforSSRCatHead ( (PSSRC_ENTRY)pRTPSession->pRTCPSession->RcvSSRCList.next, dwSSRC); } // get my own SSRC used for this stream pMySSRC = searchForMySSRC ( (PSSRC_ENTRY)pRTPSession->pRTCPSession->XmtSSRCList.prev, pRCVStruct->RTPsocket); ASSERT (pMySSRC); // is this SSRC already known on the receive list ? if (pSSRC == NULL) { // don't create an entry for my own packet looping back on // a mcast group where loopback has not been turned off if (pMySSRC->SSRC != dwSSRC) { // new party heard from. Create an entry for it pSSRC = createSSRCEntry (dwSSRC, pRTPSession->pRTCPSession, (PSOCKADDR)pRCVStruct->pFrom, (DWORD)*pRCVStruct->pFromlen, FALSE); // notify application if interested RRCMnotification (RRCM_NEW_SOURCE_EVENT, pSSRC, dwSSRC, pRTPHeader->pt); } else { // my own SSRC received back // A collision occurs if the SSRC in the rcvd packet is // equal to mine, and the network transport address is // different from mine. // A loop occurs if after a collision has been resolved the // SSRC collides again from the same source transport address pSSRCadr = (PSOCKADDR_IN)&pMySSRC->from; if (((PSOCKADDR_IN)pRCVStruct->pFrom)->sin_addr.S_un.S_addr != pSSRCadr->sin_addr.S_un.S_addr) { // check if the source address is already in the // conflicting table. This identifes that somebody out // there is looping pckts back to me if (RRCMChkCollisionTable ((PSOCKADDR)pRCVStruct->pFrom,*pRCVStruct->pFromlen, pMySSRC)) { RRCM_DBG_MSG ("RTP : Loop Detected ...", 0, NULL, 0, DBG_NOTIFY); // loop already known dwRequeue |= SSRC_LOOP_DETECTED; } else { RRCM_DBG_MSG ("RTP : Collision Detected ...", 0, NULL, 0, DBG_NOTIFY); // create new entry in conflicting address table RRCMAddEntryToCollisionTable ((PSOCKADDR)pRCVStruct->pFrom,*pRCVStruct->pFromlen, pMySSRC); // send RTCP BYE packet w/ old SSRC RTCPsendBYE (pMySSRC, "Loop/collision detected"); // select new SSRC oldSSRC = pMySSRC->SSRC; dwSSRC = getSSRC (pMySSRC->pRTCPses->RcvSSRCList, pMySSRC->pRTCPses->XmtSSRCList); EnterCriticalSection (&pMySSRC->critSect); pMySSRC->SSRC = dwSSRC; LeaveCriticalSection (&pMySSRC->critSect); // create new entry w/ old SSRC plus actual source // transport address in our receive list side, so the // packet actually en-route will be dealt with createSSRCEntry (oldSSRC, pRTPSession->pRTCPSession, (PSOCKADDR)pRCVStruct->pFrom, (DWORD)*pRCVStruct->pFromlen, FALSE); // notify application if interested RRCMnotification (RRCM_LOCAL_COLLISION_EVENT, pMySSRC, oldSSRC, 0); // loop already known dwRequeue |= SSRC_COLLISION_DETECTED; } } else { // own packet looped back because the sender joined the // multicast group and loopback is not turned off dwRequeue |= MCAST_LOOPBACK_NOT_OFF; } } } else if (pSSRC->dwSSRCStatus & THIRD_PARTY_COLLISION) { // this SSRC is marked as colliding. Reject the data dwRequeue = THIRD_PARTY_COLLISION; } if ((pSSRC != NULL) && (dwRequeue == 0)) { // do all the statistical updating stuff updateRTPStats (pRTPHeader, pSSRC, cbTransferred); // update the payload type for this SSRC pSSRC->PayLoadType = pRTPHeader->pt; // Reinstate the AppSs WSAEVENT pOverlapped->hEvent = pRCVStruct->hEvent; // And call the apps completion routine pRCVStruct->pfnCompletionNotification (dwError, cbTransferred, pOverlapped, dwFlags); // Return the struct to the free queue addToHeadOfList (&pRTPSession->pRTPFreeList, (PLINK_LIST)pRCVStruct, &pRTPSession->critSect); } // SSRCList != NULL } // valid RTP Header else { dwRequeue |= INVALID_RTP_HEADER; } if (dwRequeue) { // The RTP packet was invalid for some reason RTPpostRecvBfr (dwError, cbTransferred, pOverlapped, dwFlags); } IN_OUT_STR ("RTP : Exit RTPReceiveCallback()\n"); } /*---------------------------------------------------------------------------- * Function : saveRCVWinsockContext * Description: Saves context for this buffer so that when a completion * routine returns with a handle, we know exactly what * buffer/stream this refers to. * * Input : pOverlapped : -> to overlapped structure * pBuffers : -> to WSA buffers * pFunc : -> to completion routine * pSession : -> to the RTP session * dwBufferCount : Number of WSA buffers * pNumBytesRecvd : -> to number of bytes received * pFlags : -> to flags * pFrom : -> to the From address field * pFromlen : -> to the from address field length * RTPsocket : RTP socket descriptor * * Return: RRCM_NoError = OK. * Otherwise(!=0) = Check RRCM.h file for references. ---------------------------------------------------------------------------*/ DWORD CALLBACK saveRCVWinsockContext(LPWSAOVERLAPPED pOverlapped, LPWSABUF pBuffers, LPWSAOVERLAPPED_COMPLETION_ROUTINE pFunc, PRTP_SESSION pSession, DWORD dwBufferCount, LPDWORD pNumBytesRecvd, LPDWORD pFlags, LPVOID pFrom, LPINT pFromlen, SOCKET RTPsocket) { PRTP_BFR_LIST pNewCell; DWORD dwStatus = RRCM_NoError; DWORD numCells = NUM_FREE_CONTEXT_CELLS; IN_OUT_STR ("RTP : Enter saveRCVWinsockContext()\n"); // Get a PRTP Buffer from the free list pNewCell = (PRTP_BFR_LIST)removePcktFromTail ( (PLINK_LIST)&pSession->pRTPFreeList, &pSession->critSect); if (pNewCell == NULL) { // try to reallocate some free cells if (pSession->dwNumTimesFreeListAllocated <= MAXNUM_CONTEXT_CELLS_REALLOC) { // increment the number of reallocated times even if the realloc // fails next. Will avoid trying to realloc of a realloc problem pSession->dwNumTimesFreeListAllocated++; if (allocateLinkedList (&pSession->pRTPFreeList, pSession->hHeapFreeList, &numCells, sizeof(RTP_BFR_LIST), &pSession->critSect) == RRCM_NoError) { pNewCell = (PRTP_BFR_LIST)removePcktFromTail ( (PLINK_LIST)&pSession->pRTPFreeList, &pSession->critSect); } } } if (pNewCell != NULL) { // Initialize the params pNewCell->hEvent = pOverlapped->hEvent; pNewCell->pBuffer = pBuffers; pNewCell->pSession = pSession; pNewCell->dwFlags = *pFlags; pNewCell->pFrom = pFrom; pNewCell->pFromlen = pFromlen; pNewCell->RTPsocket = RTPsocket; pNewCell->dwBufferCount = dwBufferCount; pNewCell->pfnCompletionNotification = pFunc; // Overwrite the hEvent handed down from app. // Will return the real one when the completion routine is called pOverlapped->hEvent = (WSAEVENT)pNewCell; } else dwStatus = RRCMError_RTPResources; IN_OUT_STR ("RTP : Exit saveRCVWinsockContext()\n"); return (dwStatus); } /*---------------------------------------------------------------------------- * Function : RTPpostRecvBfr * Description: RTP post a receive buffer to Winsock * * Input : dwError : Error code * cbTransferred : Bytes transferred * pOverlapped : -> to overlapped structure * dwFlags : Flags * * Return: None ---------------------------------------------------------------------------*/ void RTPpostRecvBfr (DWORD dwError, DWORD cbTransferred, LPWSAOVERLAPPED pOverlapped, DWORD dwFlags) { DWORD dwStatus; PRTP_BFR_LIST pRCVStruct; PRTP_SESSION pRTPSession; IN_OUT_STR ("RTP : Enter RTPpostRecvBfr\n"); // Reuse the packet with another receive pRCVStruct = (PRTP_BFR_LIST)pOverlapped->hEvent; // Corresponding RTP session pRTPSession = (PRTP_SESSION)pRCVStruct->pSession; dwStatus = RRCMws.recvFrom (pRCVStruct->RTPsocket, pRCVStruct->pBuffer, pRCVStruct->dwBufferCount, &cbTransferred, &pRCVStruct->dwFlags, (PSOCKADDR)pRCVStruct->pFrom, pRCVStruct->pFromlen, pOverlapped, RTPReceiveCallback); // Check if Winsock Call succeeded if (dwStatus == SOCKET_ERROR) { // If serious error, the receive request won't proceed dwStatus = GetLastError(); if ((dwStatus != WSA_IO_PENDING) && (dwStatus != WSAEMSGSIZE)) { RRCM_DBG_MSG ("RTP : ERROR - WSARecvFrom()", dwError, __FILE__, __LINE__, DBG_ERROR); // notify the user if an error occured, so he can free up // its receive resources. The byte count is set to 0 // Reinstate the AppSs WSAEVENT pOverlapped->hEvent = pRCVStruct->hEvent; // And call the apps completion routine pRCVStruct->pfnCompletionNotification (dwStatus, 0, pOverlapped, dwFlags); // Return the receive structure to the free list addToHeadOfList (&pRTPSession->pRTPFreeList, (PLINK_LIST)pRCVStruct, &pRTPSession->critSect); } } IN_OUT_STR ("RTP : Exit RTPpostRecvBfr\n"); } #endif // [EOF]