/*++ Copyright (c) 1997 Microsoft Corporation Module Name: D:\nt\private\ntos\tdi\rawwan\core\addr.c Abstract: TDI Entry points and support routines for Address Objects. Revision History: Who When What -------- -------- ---------------------------------------------- arvindm 04-29-97 Created Notes: --*/ #include #define _FILENUMBER 'RDDA' // // Context we use to keep track of NDIS SAP registration // typedef struct _RWAN_REGISTER_SAP_CONTEXT { PRWAN_NDIS_SAP pRWanNdisSap; CO_SAP CoSap; } RWAN_REGISTER_SAP_CONTEXT, *PRWAN_REGISTER_SAP_CONTEXT; TDI_STATUS RWanTdiOpenAddress( IN PTDI_REQUEST pTdiRequest, IN TRANSPORT_ADDRESS UNALIGNED *pAddrList, IN ULONG AddrListLength, IN UINT Protocol, IN PUCHAR pOptions ) /*++ Routine Description: This is the TDI entry point for opening (creating) an Address Object. Arguments: pTdiRequest - Pointer to the TDI Request pAddrList - List of alternative addresses, one of which we're to open. AddrListLength - Length of the above Protocol - Identifies the TDI Protocol being opened. pOptions - Unused. Return Value: TDI_STATUS -- TDI_SUCCESS if a new Address Object was successfully created, TDI_BAD_ADDR if the given address isn't valid, TDI_ADDR_IN_USE if it is a duplicate. --*/ { PRWAN_TDI_PROTOCOL pProtocol; PRWAN_TDI_ADDRESS pAddrObject; TA_ADDRESS * pTransportAddress; TDI_STATUS Status; INT rc; UNREFERENCED_PARAMETER(pOptions); // // Initialize. // pAddrObject = NULL_PRWAN_TDI_ADDRESS; Status = TDI_SUCCESS; do { // // Get our protocol structure for the protocol being opened. // pProtocol = RWanGetProtocolFromNumber(Protocol); if (pProtocol == NULL_PRWAN_TDI_PROTOCOL) { RWANDEBUGP(DL_WARN, DC_ADDRESS, ("RWanTdiOpenAddress: unknown protocol number %d\n", Protocol)); Status = TDI_BAD_ADDR; break; } // // Does this protocol allow creation of address objects? // if (!pProtocol->bAllowAddressObjects) { RWANDEBUGP(DL_WARN, DC_ADDRESS, ("RWanTdiOpenAddress: Protocol %d/x%x doesn't allow addr objs\n", Protocol, pProtocol)); Status = TDI_BAD_ADDR; break; } // // Go through the given Address list and find the first one // that matches the protocol. // pTransportAddress = (*pProtocol->pAfInfo->AfChars.pAfSpGetValidTdiAddress)( pProtocol->pAfInfo->AfSpContext, pAddrList, AddrListLength ); if (pTransportAddress == NULL) { RWANDEBUGP(DL_WARN, DC_ADDRESS, ("RWanTdiOpenAddress: No valid addr for Protocol x%x in list x%x\n", pProtocol, pAddrList)); Status = TDI_BAD_ADDR; break; } RWANDEBUGP(DL_VERY_LOUD, DC_ADDRESS, ("RWanTdiOpenAddress: pProto x%x, addr x%x, type %d, length %d\n", pProtocol, pTransportAddress, pTransportAddress->AddressType, pTransportAddress->AddressLength)); // // Allocate an Address Object. // pAddrObject = RWanAllocateAddressObject(pTransportAddress); if (pAddrObject == NULL_PRWAN_TDI_ADDRESS) { RWANDEBUGP(DL_WARN, DC_ADDRESS, ("RWanTdiOpenAddress: couldnt allocate addr obj: %d bytes\n", sizeof(RWAN_TDI_ADDRESS)+pTransportAddress->AddressLength)); Status = TDI_NO_RESOURCES; break; } pAddrObject->pProtocol = pProtocol; // // Get a context for this address object from the media-specific // module. // if (pAddrObject->pProtocol->pAfInfo->AfChars.pAfSpOpenAddress) { RWAN_STATUS RWanStatus; RWanStatus = (*pAddrObject->pProtocol->pAfInfo->AfChars.pAfSpOpenAddress)( pAddrObject->pProtocol->pAfInfo->AfSpContext, (RWAN_HANDLE)pAddrObject, &(pAddrObject->AfSpAddrContext)); if (RWanStatus != RWAN_STATUS_SUCCESS) { Status = RWanToTdiStatus(RWanStatus); break; } RWAN_SET_BIT(pAddrObject->Flags, RWANF_AO_AFSP_CONTEXT_VALID); } RWAN_ACQUIRE_ADDRESS_LIST_LOCK(); // // If this is a non-NULL address, register NDIS SAPs on all // AF bindings for this protocol. // if (!((*pProtocol->pAfInfo->AfChars.pAfSpIsNullAddress)( pProtocol->pAfInfo->AfSpContext, pTransportAddress))) { // // Add a temp ref so that the address object doesn't go away. // RWanReferenceAddressObject(pAddrObject); // TdiOpenAddress temp ref Status = RWanCreateNdisSaps(pAddrObject, pProtocol); if (Status != TDI_SUCCESS) { if (RWAN_IS_BIT_SET(pAddrObject->Flags, RWANF_AO_AFSP_CONTEXT_VALID)) { (*pAddrObject->pProtocol->pAfInfo->AfChars.pAfSpCloseAddress)( pAddrObject->AfSpAddrContext); RWAN_RESET_BIT(pAddrObject->Flags, RWANF_AO_AFSP_CONTEXT_VALID); } } // // Get rid of the temp reference. // RWAN_ACQUIRE_ADDRESS_LOCK(pAddrObject); rc = RWanDereferenceAddressObject(pAddrObject); // TdiOpenAddr temp ref if (rc != 0) { RWAN_RELEASE_ADDRESS_LOCK(pAddrObject); } else { // // The address object is gone. Meaning no SAPs got registered. // pAddrObject = NULL; // // Fix up the status only if we haven't got one already. // if (Status == TDI_SUCCESS) { Status = TDI_BAD_ADDR; } } if (Status != TDI_SUCCESS) { RWAN_RELEASE_ADDRESS_LIST_LOCK(); break; } } RWAN_ASSERT(pAddrObject != NULL); RWanReferenceAddressObject(pAddrObject); // TdiOpenAddress ref // // Link this to the list of address objects on this protocol. // RWAN_INSERT_HEAD_LIST(&(pProtocol->AddrObjList), &(pAddrObject->AddrLink)); RWAN_RELEASE_ADDRESS_LIST_LOCK(); // // Fix up all return values. // pTdiRequest->Handle.AddressHandle = (PVOID)pAddrObject; break; } while (FALSE); if (Status != TDI_SUCCESS) { // // Clean up. // if (pAddrObject != NULL_PRWAN_TDI_ADDRESS) { RWAN_FREE_MEM(pAddrObject); } RWANDEBUGP(DL_FATAL, DC_WILDCARD, ("OpenAddr: failure status %x\n", Status)); } return (Status); } TDI_STATUS RWanTdiSetEvent( IN PVOID AddrObjContext, IN INT TdiEventType, IN PVOID Handler, IN PVOID HandlerContext ) /*++ Routine Description: Set an event handler (up-call) for an address object. Arguments: AddrObjContext - Our context for an Address Object (pointer to it). TdiEventType - The TDI Event for which we are given an up-call handler. Handler - The handler function HandlerContext - Context to be passed to the handler function. Return Value: TDI_STATUS - TDI_SUCCESS if the event type is a supported one, else TDI_BAD_EVENT_TYPE --*/ { PRWAN_TDI_ADDRESS pAddrObject; TDI_STATUS Status; pAddrObject = (PRWAN_TDI_ADDRESS)AddrObjContext; RWAN_STRUCT_ASSERT(pAddrObject, nta); Status = TDI_SUCCESS; RWAN_ACQUIRE_ADDRESS_LOCK(pAddrObject); switch (TdiEventType) { case TDI_EVENT_CONNECT: RWANDEBUGP(DL_VERY_LOUD, DC_ADDRESS, ("SetEvent[CONN IND]: pAddrObject x%x, Handler x%x, Ctxt x%x\n", pAddrObject, Handler, HandlerContext)); pAddrObject->pConnInd = Handler; pAddrObject->ConnIndContext = HandlerContext; break; case TDI_EVENT_DISCONNECT: RWANDEBUGP(DL_VERY_LOUD, DC_ADDRESS, ("SetEvent[DISC IND]: pAddrObject x%x, Handler x%x, Ctxt x%x\n", pAddrObject, Handler, HandlerContext)); pAddrObject->pDisconInd = Handler; pAddrObject->DisconIndContext = HandlerContext; break; case TDI_EVENT_ERROR: RWANDEBUGP(DL_VERY_LOUD, DC_ADDRESS, ("SetEvent[ERRORIND]: pAddrObject x%x, Handler x%x, Ctxt x%x\n", pAddrObject, Handler, HandlerContext)); pAddrObject->pErrorInd = Handler; pAddrObject->ErrorIndContext = HandlerContext; break; case TDI_EVENT_RECEIVE: RWANDEBUGP(DL_VERY_LOUD, DC_ADDRESS, ("SetEvent[RECV IND]: pAddrObject x%x, Handler x%x, Ctxt x%x\n", pAddrObject, Handler, HandlerContext)); pAddrObject->pRcvInd = Handler; pAddrObject->RcvIndContext = HandlerContext; break; default: Status = TDI_BAD_EVENT_TYPE; break; } RWAN_RELEASE_ADDRESS_LOCK(pAddrObject); return (Status); } TDI_STATUS RWanTdiCloseAddress( IN PTDI_REQUEST pTdiRequest ) /*++ Routine Description: This is the TDI entry point for closing (deleting) an Address Object. Arguments: pTdiRequest - Pointer to the TDI Request Return Value: TDI_STATUS -- TDI_SUCCESS if we successfully deleted the address object immediately, TDI_PENDING if we have to complete some operations (e.g. deregister NDIS SAP) before we can complete this. --*/ { TDI_STATUS Status; PRWAN_TDI_ADDRESS pAddrObject; PRWAN_TDI_PROTOCOL pProtocol; INT rc; #if DBG RWAN_IRQL EntryIrq, ExitIrq; #endif // DBG RWAN_GET_ENTRY_IRQL(EntryIrq); pAddrObject = (PRWAN_TDI_ADDRESS)pTdiRequest->Handle.AddressHandle; RWAN_STRUCT_ASSERT(pAddrObject, nta); pProtocol = pAddrObject->pProtocol; RWANDEBUGP(DL_EXTRA_LOUD, DC_BIND, ("TdiCloseAddr: pAddrObj x%x, RefCnt %d\n", pAddrObject, pAddrObject->RefCount)); // // Delete this from the list of address objects on this protocol. // RWAN_ACQUIRE_ADDRESS_LIST_LOCK(); RWAN_DELETE_FROM_LIST(&(pAddrObject->AddrLink)); RWAN_RELEASE_ADDRESS_LIST_LOCK(); // // Tell the media-specific module that this address object is closing. // if (RWAN_IS_BIT_SET(pAddrObject->Flags, RWANF_AO_AFSP_CONTEXT_VALID)) { (*pAddrObject->pProtocol->pAfInfo->AfChars.pAfSpCloseAddress)( pAddrObject->AfSpAddrContext); RWAN_RESET_BIT(pAddrObject->Flags, RWANF_AO_AFSP_CONTEXT_VALID); } RWAN_ACQUIRE_ADDRESS_LOCK(pAddrObject); #if DBG if (!RWAN_IS_LIST_EMPTY(&pAddrObject->SapList) || !RWAN_IS_LIST_EMPTY(&pAddrObject->IdleConnList) || !RWAN_IS_LIST_EMPTY(&pAddrObject->ListenConnList) || !RWAN_IS_LIST_EMPTY(&pAddrObject->ActiveConnList) ) { RWAN_ASSERT(pAddrObject->RefCount > 1); } #endif // DBG rc = RWanDereferenceAddressObject(pAddrObject); // CloseAddress deref if (rc == 0) { Status = TDI_SUCCESS; } else { // // Mark this address object as closing, so that we // complete this operation when the reference count // falls to 0. // RWAN_SET_BIT(pAddrObject->Flags, RWANF_AO_CLOSING); RWANDEBUGP(DL_LOUD, DC_BIND, ("TdiCloseAddr: will pend, pAddrObj x%x, RefCnt %d, DelNotify x%x\n", pAddrObject, pAddrObject->RefCount, pTdiRequest->RequestNotifyObject)); RWAN_SET_DELETE_NOTIFY(&pAddrObject->DeleteNotify, pTdiRequest->RequestNotifyObject, pTdiRequest->RequestContext); // // Deregister all NDIS SAPs attached to this Address Object. // RWanDeleteNdisSaps(pAddrObject); Status = TDI_PENDING; } RWAN_CHECK_EXIT_IRQL(EntryIrq, ExitIrq); return (Status); } TDI_STATUS RWanCreateNdisSaps( IN PRWAN_TDI_ADDRESS pAddrObject, IN PRWAN_TDI_PROTOCOL pProtocol ) /*++ Routine Description: Create NDIS SAPs on behalf of the given TDI Address Object. We create NDIS SAPs on all AF opens that match the specified TDI protocol. Arguments: pAddrObject - Pointer to our TDI Address Object pProtocol - Pointer to TDI protocol to which the addr object belongs Return Value: TDI_STATUS -- this is TDI_SUCCESS if we started SAP registration on atleast one NDIS AF open, TDI_NOT_ASSOCIATED otherwise. --*/ { TDI_STATUS Status; PCO_SAP pCoSap; PRWAN_NDIS_SAP pSap; PLIST_ENTRY pSapEntry; PLIST_ENTRY pNextSapEntry; PRWAN_NDIS_ADAPTER pAdapter; PLIST_ENTRY pAdEntry; PRWAN_NDIS_AF pAf; PLIST_ENTRY pAfEntry; PRWAN_NDIS_AF_INFO pAfInfo; pAfInfo = pProtocol->pAfInfo; RWANDEBUGP(DL_VERY_LOUD, DC_BIND, ("CreateNdisSaps: pAddrObject x%x, pProtocol x%x, pAfInfo x%x\n", pAddrObject, pProtocol, pAfInfo)); RWAN_ASSERT(RWAN_IS_LIST_EMPTY(&pAddrObject->SapList)); RWAN_ACQUIRE_GLOBAL_LOCK(); // // Prepare NDIS SAP structures for each NDIS AF open that matches // this protocol. // for (pAdEntry = pRWanGlobal->AdapterList.Flink; pAdEntry != &(pRWanGlobal->AdapterList); pAdEntry = pAdEntry->Flink) { pAdapter = CONTAINING_RECORD(pAdEntry, RWAN_NDIS_ADAPTER, AdapterLink); RWANDEBUGP(DL_EXTRA_LOUD, DC_BIND, ("CreateNdisSaps: looking at adapter x%x\n", pAdapter)); for (pAfEntry = pAdapter->AfList.Flink; pAfEntry != &(pAdapter->AfList); pAfEntry = pAfEntry->Flink) { pAf = CONTAINING_RECORD(pAfEntry, RWAN_NDIS_AF, AfLink); RWANDEBUGP(DL_EXTRA_LOUD, DC_BIND, ("CreateNdisSaps: looking at AF x%x, AfInfo x%x\n", pAf, pAf->pAfInfo)); if (pAf->pAfInfo == pAfInfo) { // // This NDIS AF open matches the TDI protocol for which // the address object is opened. We will create an NDIS // SAP here. // ULONG SapSize; // // Allocate a new SAP structure. // SapSize = sizeof(RWAN_NDIS_SAP); RWAN_ALLOC_MEM(pSap, RWAN_NDIS_SAP, SapSize); if (pSap == NULL_PRWAN_NDIS_SAP) { RWANDEBUGP(DL_WARN, DC_ADDRESS, ("RWanCreateNdisSaps: failed to alloc SAP %d bytes\n", SapSize)); continue; } // // Fill it in. // RWAN_SET_SIGNATURE(pSap, nsp); pSap->pAddrObject = pAddrObject; pSap->NdisSapHandle = NULL; pSap->pNdisAf = pAf; pSap->pCoSap = NULL; // // Link to all SAPs associated with address object. // RWAN_INSERT_TAIL_LIST(&(pAddrObject->SapList), &(pSap->AddrObjLink)); RWanReferenceAddressObject(pAddrObject); // NDIS SAP ref } } } RWAN_RELEASE_GLOBAL_LOCK(); // // Now go through the SAP list and call NDIS to register them. // for (pSapEntry = pAddrObject->SapList.Flink; pSapEntry != &(pAddrObject->SapList); pSapEntry = pNextSapEntry) { RWAN_STATUS RWanStatus; NDIS_STATUS NdisStatus; pSap = CONTAINING_RECORD(pSapEntry, RWAN_NDIS_SAP, AddrObjLink); pNextSapEntry = pSap->AddrObjLink.Flink; // // Convert the transport address to NDIS SAP format. // RWanStatus = (*pAfInfo->AfChars.pAfSpTdi2NdisSap)( pAfInfo->AfSpContext, pAddrObject->AddressType, pAddrObject->AddressLength, pAddrObject->pAddress, &(pSap->pCoSap)); if (RWanStatus == RWAN_STATUS_SUCCESS) { RWAN_ASSERT(pSap->pCoSap != NULL); // // Register this SAP with the Call Manager. // NdisStatus = NdisClRegisterSap( pSap->pNdisAf->NdisAfHandle, (NDIS_HANDLE)pSap, pSap->pCoSap, &(pSap->NdisSapHandle) ); } else { NdisStatus = NDIS_STATUS_FAILURE; } if (NdisStatus != NDIS_STATUS_PENDING) { RWanNdisRegisterSapComplete( NdisStatus, (NDIS_HANDLE)pSap, pSap->pCoSap, pSap->NdisSapHandle ); } } if (!RWAN_IS_LIST_EMPTY(&pAddrObject->SapList)) { Status = TDI_SUCCESS; } else { Status = RWanNdisToTdiStatus(pAddrObject->SapStatus); RWANDEBUGP(DL_WARN, DC_WILDCARD, ("CreateNdisSaps: NdisStatus %x, TdiStatus %x\n", pAddrObject->SapStatus, Status)); } return (Status); } VOID RWanNdisRegisterSapComplete( IN NDIS_STATUS NdisStatus, IN NDIS_HANDLE OurSapContext, IN PCO_SAP pCoSap, IN NDIS_HANDLE NdisSapHandle ) /*++ Routine Description: This is called by NDIS to signal completion of a previously pended call to NdisClRegisterSap. Arguments: NdisStatus - Final status of SAP registration. OurSapContext - Points to our NDIS SAP structure. pCoSap - The parameter we passed to NdisClRegisterSap. Not used. NdisSapHandle - If NdisStatus indicates success, this contains the assigned handle for this SAP. Return Value: None --*/ { PRWAN_NDIS_SAP pSap; PRWAN_TDI_ADDRESS pAddrObject; INT rc; PRWAN_NDIS_AF_INFO pAfInfo; PRWAN_NDIS_AF pAf; UNREFERENCED_PARAMETER(pCoSap); pSap = (PRWAN_NDIS_SAP)OurSapContext; RWAN_STRUCT_ASSERT(pSap, nsp); pAddrObject = pSap->pAddrObject; pAfInfo = pSap->pNdisAf->pAfInfo; pCoSap = pSap->pCoSap; pSap->pCoSap = NULL; RWANDEBUGP(DL_LOUD, DC_BIND, ("RegisterSapComplete: pAddrObj x%x, pSap x%x, Status x%x\n", pAddrObject, pSap, NdisStatus)); if (NdisStatus == NDIS_STATUS_SUCCESS) { pSap->NdisSapHandle = NdisSapHandle; pAf = pSap->pNdisAf; // // Link this SAP to the list of all SAPs on the AF. // RWAN_ACQUIRE_AF_LOCK(pAf); RWAN_INSERT_TAIL_LIST(&pAf->NdisSapList, &pSap->AfLink); RWanReferenceAf(pAf); // New SAP registered. RWAN_RELEASE_AF_LOCK(pAf); } else { // // Failed to register this SAP. Clean up. // RWAN_ACQUIRE_ADDRESS_LOCK(pAddrObject); pAddrObject->SapStatus = NdisStatus; RWAN_DELETE_FROM_LIST(&(pSap->AddrObjLink)); rc = RWanDereferenceAddressObject(pAddrObject); // Reg SAP failed if (rc != 0) { RWAN_RELEASE_ADDRESS_LOCK(pAddrObject); } RWAN_FREE_MEM(pSap); } // // If the AF-specific module had given us a SAP structure, // return it now. // if (pCoSap != NULL) { (*pAfInfo->AfChars.pAfSpReturnNdisSap)( pAfInfo->AfSpContext, pCoSap ); } return; } VOID RWanDeleteNdisSaps( IN PRWAN_TDI_ADDRESS pAddrObject ) /*++ Routine Description: Delete all NDIS SAPs on the given address object. We call NDIS to deregister them. Arguments: pAddrObject - Pointer to TDI Address Object Return Value: None --*/ { PRWAN_NDIS_SAP pSap; PLIST_ENTRY pSapEntry; PLIST_ENTRY pFirstSapEntry; PLIST_ENTRY pNextSapEntry; NDIS_STATUS NdisStatus; NDIS_HANDLE NdisSapHandle; // // Mark all SAPs as closing, while we hold a lock to the address object. // for (pSapEntry = pAddrObject->SapList.Flink; pSapEntry != &(pAddrObject->SapList); pSapEntry = pNextSapEntry) { pSap = CONTAINING_RECORD(pSapEntry, RWAN_NDIS_SAP, AddrObjLink); pNextSapEntry = pSap->AddrObjLink.Flink; RWAN_SET_BIT(pSap->Flags, RWANF_SAP_CLOSING); } // // Unlink the SAP list from the Address Object. // This will protect us if at all we re-enter this routine. // pFirstSapEntry = pAddrObject->SapList.Flink; RWAN_INIT_LIST(&pAddrObject->SapList); RWAN_RELEASE_ADDRESS_LOCK(pAddrObject); for (pSapEntry = pFirstSapEntry; pSapEntry != &(pAddrObject->SapList); pSapEntry = pNextSapEntry) { pSap = CONTAINING_RECORD(pSapEntry, RWAN_NDIS_SAP, AddrObjLink); pNextSapEntry = pSap->AddrObjLink.Flink; NdisSapHandle = pSap->NdisSapHandle; RWAN_ASSERT(NdisSapHandle != NULL); RWANDEBUGP(DL_LOUD, DC_BIND, ("RWanDeleteNdisSaps: pAddrObj x%x, pSap x%x, pAf x%x\n", pAddrObject, pSap, pSap->pNdisAf)); NdisStatus = NdisClDeregisterSap(NdisSapHandle); if (NdisStatus != NDIS_STATUS_PENDING) { RWanNdisDeregisterSapComplete( NdisStatus, (NDIS_HANDLE)pSap ); } } } VOID RWanNdisDeregisterSapComplete( IN NDIS_STATUS NdisStatus, IN NDIS_HANDLE ProtocolSapContext ) /*++ Routine Description: This is called by NDIS to signal completion of a previously pended call to NdisClDeregisterSap. We unlink the SAP from the two lists it is linked to: the Address Object's SAP list and the AF's SAP list. Arguments: NdisStatus - Final status of SAP deregistration. Return Value: None --*/ { PRWAN_NDIS_SAP pSap; PRWAN_TDI_ADDRESS pAddrObject; PRWAN_NDIS_AF pAf; INT rc; RWAN_ASSERT(NdisStatus == NDIS_STATUS_SUCCESS); pSap = (PRWAN_NDIS_SAP)ProtocolSapContext; RWAN_STRUCT_ASSERT(pSap, nsp); RWANDEBUGP(DL_VERY_LOUD, DC_BIND, ("RWanDeregSapComplete: pSap x%x, pAddrObj x%x, pAf x%x\n", pSap, pSap->pAddrObject, pSap->pNdisAf)); pAddrObject = pSap->pAddrObject; // // Unlink the SAP from the Address Object. // RWAN_ACQUIRE_ADDRESS_LOCK(pAddrObject); RWAN_DELETE_FROM_LIST(&(pSap->AddrObjLink)); rc = RWanDereferenceAddressObject(pAddrObject); // SAP dereg complete if (rc != 0) { RWAN_RELEASE_ADDRESS_LOCK(pAddrObject); } // // Unlink the SAP from the AF. // pAf = pSap->pNdisAf; RWAN_STRUCT_ASSERT(pAf, naf); RWAN_ACQUIRE_AF_LOCK(pAf); RWAN_DELETE_FROM_LIST(&(pSap->AfLink)); rc = RWanDereferenceAf(pAf); // SAP deregister complete if (rc != 0) { RWAN_RELEASE_AF_LOCK(pAf); } RWAN_FREE_MEM(pSap); }