diff --git a/src/include/com/amazonaws/kinesis/video/webrtcclient/Include.h b/src/include/com/amazonaws/kinesis/video/webrtcclient/Include.h index b5bc54404c..943a5757a6 100644 --- a/src/include/com/amazonaws/kinesis/video/webrtcclient/Include.h +++ b/src/include/com/amazonaws/kinesis/video/webrtcclient/Include.h @@ -180,7 +180,7 @@ extern "C" { #define STATUS_TURN_MISSING_CHANNEL_DATA_HEADER STATUS_ICE_BASE + 0x00000024 #define STATUS_ICE_FAILED_TO_RECOVER_FROM_DISCONNECTION STATUS_ICE_BASE + 0x00000025 #define STATUS_ICE_NO_AVAILABLE_ICE_CANDIDATE_PAIR STATUS_ICE_BASE + 0x00000026 -#define STATUS_TURN_CONNECTION_STATE_NOT_READY_TO_SEND_DATA STATUS_ICE_BASE + 0x00000027 +#define STATUS_TURN_CONNECTION_PEER_NOT_USABLE STATUS_ICE_BASE + 0x00000027 /*!@} */ /*===========================================================================================*/ diff --git a/src/source/Ice/IceAgent.c b/src/source/Ice/IceAgent.c index 238a306835..246a8534a3 100644 --- a/src/source/Ice/IceAgent.c +++ b/src/source/Ice/IceAgent.c @@ -950,34 +950,26 @@ STATUS iceAgentSendStunPacket(PStunPacket pStunPacket, PBYTE password, UINT32 pa STATUS retStatus = STATUS_SUCCESS; UINT32 stunPacketSize = STUN_PACKET_ALLOCATION_SIZE; BYTE stunPacketBuffer[STUN_PACKET_ALLOCATION_SIZE]; - KvsIpAddress destAddr; - SocketConnection socketConnection; - BOOL isRelay = FALSE; PIceCandidatePair pIceCandidatePair = NULL; // Assuming holding pIceAgent->lock CHK(pStunPacket != NULL && pIceAgent != NULL && pLocalCandidate != NULL && pDestAddr != NULL, STATUS_NULL_ARG); - // Construct context - // Stun Binding Indication seems to not expect any response. Therefore not storing transactionId CHK_STATUS(iceUtilsPackageStunPacket(pStunPacket, password, passwordLen, stunPacketBuffer, &stunPacketSize)); - socketConnection = *pLocalCandidate->pSocketConnection; - destAddr = *pDestAddr; - isRelay = pLocalCandidate->iceCandidateType == ICE_CANDIDATE_TYPE_RELAYED; - retStatus = iceUtilsSendData((PBYTE) stunPacketBuffer, stunPacketSize, - &destAddr, - &socketConnection, + pDestAddr, + pLocalCandidate->pSocketConnection, pIceAgent->turnConnectionTracker.pTurnConnection, - isRelay); + pLocalCandidate->iceCandidateType == ICE_CANDIDATE_TYPE_RELAYED); if (STATUS_FAILED(retStatus)) { - DLOGW("iceUtilsSendData failed with 0x%08x", retStatus); + DLOGW("iceUtilsSendData failed with 0x%08x. Mark candidate pair as failed.", retStatus); retStatus = STATUS_SUCCESS; - // Update iceCandidatePair state to failed. pIceCandidatePair could no longer exist. + /* Update iceCandidatePair state to failed. + * pIceCandidatePair could no longer exist. */ CHK_STATUS(findIceCandidatePairWithLocalSocketConnectionAndRemoteAddr(pIceAgent, pLocalCandidate->pSocketConnection, pDestAddr, TRUE, &pIceCandidatePair)); if (pIceCandidatePair != NULL) { diff --git a/src/source/Ice/TurnConnection.c b/src/source/Ice/TurnConnection.c index 8615536123..6eb2b2342c 100644 --- a/src/source/Ice/TurnConnection.c +++ b/src/source/Ice/TurnConnection.c @@ -339,6 +339,8 @@ STATUS turnConnectionHandleStunError(PTurnConnection pTurnConnection, PBYTE pBuf PStunAttributeRealm pStunAttributeRealm = NULL; PStunPacket pStunPacket = NULL; BOOL locked = FALSE; + PTurnPeer pTurnPeer = NULL; + PDoubleListNode pCurNode = NULL; CHK(pTurnConnection != NULL, STATUS_NULL_ARG); CHK(pBuffer != NULL && bufferLen > 0, STATUS_INVALID_ARG); @@ -350,49 +352,68 @@ STATUS turnConnectionHandleStunError(PTurnConnection pTurnConnection, PBYTE pBuf if (pTurnConnection->credentialObtained) { retStatus = deserializeStunPacket(pBuffer, bufferLen, pTurnConnection->longTermKey, MD5_DIGEST_LENGTH, &pStunPacket); } - // if deserializing with password didnt work, try deserialize without password again + /* if deserializing with password didnt work, try deserialize without password again */ if (!pTurnConnection->credentialObtained || STATUS_FAILED(retStatus)) { CHK_STATUS(deserializeStunPacket(pBuffer, bufferLen, NULL, 0, &pStunPacket)); retStatus = STATUS_SUCCESS; } CHK_STATUS(getStunAttribute(pStunPacket, STUN_ATTRIBUTE_TYPE_ERROR_CODE, &pStunAttr)); - CHK_WARN(pStunAttr != NULL, retStatus, "No error code attribute found in Stun Error response. Dropping Packet"); pStunAttributeErrorCode = (PStunAttributeErrorCode) pStunAttr; - DLOGW("Received STUN error response. Error type: 0x%02x, Error Code: %u. Error detail: %s.", - stunPacketType, pStunAttributeErrorCode->errorCode, pStunAttributeErrorCode->errorPhrase); - - if (pStunAttributeErrorCode->errorCode == STUN_ERROR_UNAUTHORIZED) { - CHK_STATUS(getStunAttribute(pStunPacket, STUN_ATTRIBUTE_TYPE_NONCE, &pStunAttr)); - CHK_WARN(pStunAttr != NULL, retStatus, "No Nonce attribute found in Allocate Error response. Dropping Packet"); - pStunAttributeNonce = (PStunAttributeNonce) pStunAttr; - CHK_WARN(pStunAttributeNonce->attribute.length <= STUN_MAX_NONCE_LEN, retStatus, "Invalid Nonce found in Allocate Error response. Dropping Packet"); - pTurnConnection->nonceLen = pStunAttributeNonce->attribute.length; - MEMCPY(pTurnConnection->turnNonce, pStunAttributeNonce->nonce, pTurnConnection->nonceLen); - - CHK_STATUS(getStunAttribute(pStunPacket, STUN_ATTRIBUTE_TYPE_REALM, &pStunAttr)); - CHK_WARN(pStunAttr != NULL, retStatus, "No Realm attribute found in Allocate Error response. Dropping Packet"); - pStunAttributeRealm = (PStunAttributeRealm) pStunAttr; - CHK_WARN(pStunAttributeRealm->attribute.length <= STUN_MAX_REALM_LEN, retStatus, "Invalid Realm found in Allocate Error response. Dropping Packet"); - // pStunAttributeRealm->attribute.length does not include null terminator and pStunAttributeRealm->realm is not null terminated - STRNCPY(pTurnConnection->turnRealm, pStunAttributeRealm->realm, pStunAttributeRealm->attribute.length); - pTurnConnection->turnRealm[pStunAttributeRealm->attribute.length] = '\0'; - - pTurnConnection->credentialObtained = TRUE; - - CHK_STATUS(turnConnectionUpdateNonce(pTurnConnection)); - - } else if (pStunAttributeErrorCode->errorCode == STUN_ERROR_STALE_NONCE) { - DLOGD("Updating stale nonce"); - CHK_STATUS(getStunAttribute(pStunPacket, STUN_ATTRIBUTE_TYPE_NONCE, &pStunAttr)); - CHK_WARN(pStunAttr != NULL, retStatus, "No Nonce attribute found in Refresh Error response. Dropping Packet"); - pStunAttributeNonce = (PStunAttributeNonce) pStunAttr; - CHK_WARN(pStunAttributeNonce->attribute.length <= STUN_MAX_NONCE_LEN, retStatus, "Invalid Nonce found in Refresh Error response. Dropping Packet"); - pTurnConnection->nonceLen = pStunAttributeNonce->attribute.length; - MEMCPY(pTurnConnection->turnNonce, pStunAttributeNonce->nonce, pTurnConnection->nonceLen); - - CHK_STATUS(turnConnectionUpdateNonce(pTurnConnection)); + + switch (pStunAttributeErrorCode->errorCode) { + case STUN_ERROR_UNAUTHORIZED: + CHK_STATUS(getStunAttribute(pStunPacket, STUN_ATTRIBUTE_TYPE_NONCE, &pStunAttr)); + CHK_WARN(pStunAttr != NULL, retStatus, "No Nonce attribute found in Allocate Error response. Dropping Packet"); + pStunAttributeNonce = (PStunAttributeNonce) pStunAttr; + CHK_WARN(pStunAttributeNonce->attribute.length <= STUN_MAX_NONCE_LEN, retStatus, "Invalid Nonce found in Allocate Error response. Dropping Packet"); + pTurnConnection->nonceLen = pStunAttributeNonce->attribute.length; + MEMCPY(pTurnConnection->turnNonce, pStunAttributeNonce->nonce, pTurnConnection->nonceLen); + + CHK_STATUS(getStunAttribute(pStunPacket, STUN_ATTRIBUTE_TYPE_REALM, &pStunAttr)); + CHK_WARN(pStunAttr != NULL, retStatus, "No Realm attribute found in Allocate Error response. Dropping Packet"); + pStunAttributeRealm = (PStunAttributeRealm) pStunAttr; + CHK_WARN(pStunAttributeRealm->attribute.length <= STUN_MAX_REALM_LEN, retStatus, "Invalid Realm found in Allocate Error response. Dropping Packet"); + // pStunAttributeRealm->attribute.length does not include null terminator and pStunAttributeRealm->realm is not null terminated + STRNCPY(pTurnConnection->turnRealm, pStunAttributeRealm->realm, pStunAttributeRealm->attribute.length); + pTurnConnection->turnRealm[pStunAttributeRealm->attribute.length] = '\0'; + + pTurnConnection->credentialObtained = TRUE; + + CHK_STATUS(turnConnectionUpdateNonce(pTurnConnection)); + break; + + case STUN_ERROR_STALE_NONCE: + DLOGD("Updating stale nonce"); + CHK_STATUS(getStunAttribute(pStunPacket, STUN_ATTRIBUTE_TYPE_NONCE, &pStunAttr)); + CHK_WARN(pStunAttr != NULL, retStatus, "No Nonce attribute found in Refresh Error response. Dropping Packet"); + pStunAttributeNonce = (PStunAttributeNonce) pStunAttr; + CHK_WARN(pStunAttributeNonce->attribute.length <= STUN_MAX_NONCE_LEN, retStatus, "Invalid Nonce found in Refresh Error response. Dropping Packet"); + pTurnConnection->nonceLen = pStunAttributeNonce->attribute.length; + MEMCPY(pTurnConnection->turnNonce, pStunAttributeNonce->nonce, pTurnConnection->nonceLen); + + CHK_STATUS(turnConnectionUpdateNonce(pTurnConnection)); + break; + + default: + /* Remove peer for any other error */ + DLOGW("Received STUN error response. Error type: 0x%02x, Error Code: %u. attribute len %u, Error detail: %s.", + stunPacketType, pStunAttributeErrorCode->errorCode, pStunAttributeErrorCode->attribute.length, + pStunAttributeErrorCode->errorPhrase); + + /* Find TurnPeer using transaction Id, then mark it as failed */ + doubleListGetHeadNode(pTurnConnection->turnPeerList, &pCurNode); + while (pCurNode != NULL) { + pTurnPeer = (PTurnPeer) pCurNode->data; + pCurNode = pCurNode->pNext; + if (transactionIdStoreHasId(pTurnPeer->pTransactionIdStore, pBuffer + STUN_PACKET_TRANSACTION_ID_OFFSET)) { + pTurnPeer->connectionState = TURN_PEER_CONN_STATE_FAILED; + /* break the loop */ + pCurNode = NULL; + } + } + break; } CleanUp: @@ -572,10 +593,8 @@ STATUS turnConnectionAddPeer(PTurnConnection pTurnConnection, PKvsIpAddress pPee ENTERS(); STATUS retStatus = STATUS_SUCCESS; PTurnPeer pTurnPeer = NULL; - BOOL locked = FALSE, duplicatedPeer = FALSE; - PDoubleListNode pCurNode = NULL; - UINT64 data; - UINT16 peerCount = 0; + BOOL locked = FALSE; + UINT32 peerCount = 0; CHK(pTurnConnection != NULL && pPeerAddress != NULL, STATUS_NULL_ARG); CHK(pTurnConnection->turnServer.ipAddress.family == pPeerAddress->family, STATUS_INVALID_ARG); @@ -584,20 +603,11 @@ STATUS turnConnectionAddPeer(PTurnConnection pTurnConnection, PKvsIpAddress pPee MUTEX_LOCK(pTurnConnection->lock); locked = TRUE; - // check for duplicate - CHK_STATUS(doubleListGetHeadNode(pTurnConnection->turnPeerList, &pCurNode)); - while (pCurNode != NULL) { - CHK_STATUS(doubleListGetNodeData(pCurNode, &data)); - pCurNode = pCurNode->pNext; - - pTurnPeer = (PTurnPeer) data; - if (isSameIpAddress(&pTurnPeer->address, pPeerAddress, TRUE)) { - duplicatedPeer = TRUE; - break; - } - peerCount++; - } - CHK(!duplicatedPeer, retStatus); + /* check for duplicate */ + CHK(turnConnectionGetPeerWithIp(pTurnConnection, pPeerAddress) == NULL, retStatus); + CHK_STATUS(doubleListGetNodeCount(pTurnConnection->turnPeerList, &peerCount)); + CHK_WARN(peerCount < DEFAULT_TURN_MAX_PEER_COUNT, STATUS_INVALID_OPERATION, + "Add peer failed. Max peer count reached"); pTurnPeer = (PTurnPeer) MEMCALLOC(1, SIZEOF(TurnPeer)); CHK(pTurnPeer != NULL, STATUS_NOT_ENOUGH_MEMORY); @@ -606,11 +616,12 @@ STATUS turnConnectionAddPeer(PTurnConnection pTurnConnection, PKvsIpAddress pPee pTurnPeer->connectionState = TURN_PEER_CONN_STATE_CREATE_PERMISSION; pTurnPeer->address = *pPeerAddress; pTurnPeer->xorAddress = *pPeerAddress; - pTurnPeer->channelNumber = peerCount + TURN_CHANNEL_BIND_CHANNEL_NUMBER_BASE; + /* safe to down cast because DEFAULT_TURN_MAX_PEER_COUNT is enforced */ + pTurnPeer->channelNumber = (UINT16) peerCount + TURN_CHANNEL_BIND_CHANNEL_NUMBER_BASE; pTurnPeer->permissionExpirationTime = INVALID_TIMESTAMP_VALUE; pTurnPeer->ready = FALSE; - CHK_STATUS(xorIpAddress(&pTurnPeer->xorAddress, NULL)); // only work for IPv4 for now + CHK_STATUS(xorIpAddress(&pTurnPeer->xorAddress, NULL)); /* only work for IPv4 for now */ CHK_STATUS(createTransactionIdStore(DEFAULT_MAX_STORED_TRANSACTION_ID_COUNT, &pTurnPeer->pTransactionIdStore)); CHK_STATUS(doubleListInsertItemTail(pTurnConnection->turnPeerList, (UINT64) pTurnPeer)); @@ -634,9 +645,7 @@ STATUS turnConnectionAddPeer(PTurnConnection pTurnConnection, PKvsIpAddress pPee STATUS turnConnectionSendData(PTurnConnection pTurnConnection, PBYTE pBuf, UINT32 bufLen, PKvsIpAddress pDestIp) { STATUS retStatus = STATUS_SUCCESS; - PDoubleListNode pCurNode = NULL; - UINT64 data; - PTurnPeer pTurnPeer = NULL, pSendPeer = NULL; + PTurnPeer pSendPeer = NULL; UINT32 paddedDataLen = 0; CHAR ipAddrStr[KVS_IP_ADDRESS_STRING_BUFFER_LEN]; BOOL locked = FALSE; @@ -656,18 +665,7 @@ STATUS turnConnectionSendData(PTurnConnection pTurnConnection, PBYTE pBuf, UINT3 MUTEX_LOCK(pTurnConnection->lock); locked = TRUE; - // find TurnPeer with pDestIp - CHK_STATUS(doubleListGetHeadNode(pTurnConnection->turnPeerList, &pCurNode)); - while (pCurNode != NULL) { - CHK_STATUS(doubleListGetNodeData(pCurNode, &data)); - pCurNode = pCurNode->pNext; - - pTurnPeer = (PTurnPeer) data; - if (isSameIpAddress(&pTurnPeer->address, pDestIp, TRUE)) { - pSendPeer = pTurnPeer; - break; - } - } + pSendPeer = turnConnectionGetPeerWithIp(pTurnConnection, pDestIp); MUTEX_UNLOCK(pTurnConnection->lock); locked = FALSE; @@ -677,13 +675,15 @@ STATUS turnConnectionSendData(PTurnConnection pTurnConnection, PBYTE pBuf, UINT3 DLOGV("Unable to send data through turn because peer with address %s:%u is not found", ipAddrStr, KVS_GET_IP_ADDRESS_PORT(pDestIp)); CHK(FALSE, retStatus); + } else if (pSendPeer->connectionState == TURN_PEER_CONN_STATE_FAILED) { + CHK(FALSE, STATUS_TURN_CONNECTION_PEER_NOT_USABLE); } else if (!pSendPeer->ready) { DLOGV("Unable to send data through turn because turn channel is not established with peer with address %s:%u", ipAddrStr, KVS_GET_IP_ADDRESS_PORT(pDestIp)); CHK(FALSE, retStatus); } - // need to serialize send because every send load data into the same buffer pTurnConnection->sendDataBuffer + /* need to serialize send because every send load data into the same buffer pTurnConnection->sendDataBuffer */ MUTEX_LOCK(pTurnConnection->sendLock); sendLocked = TRUE; @@ -691,7 +691,7 @@ STATUS turnConnectionSendData(PTurnConnection pTurnConnection, PBYTE pBuf, UINT3 paddedDataLen = (UINT32) ROUND_UP(TURN_DATA_CHANNEL_SEND_OVERHEAD + bufLen, 4); - // generate data channel TURN message + /* generate data channel TURN message */ putInt16((PINT16) (pTurnConnection->sendDataBuffer), pSendPeer->channelNumber); putInt16((PINT16) (pTurnConnection->sendDataBuffer + 2), (UINT16) bufLen); MEMCPY(pTurnConnection->sendDataBuffer + TURN_DATA_CHANNEL_SEND_OVERHEAD, pBuf, bufLen); @@ -1421,3 +1421,25 @@ PTurnPeer turnConnectionGetPeerWithChannelNumber(PTurnConnection pTurnConnection return pTurnPeer; } + +PTurnPeer turnConnectionGetPeerWithIp(PTurnConnection pTurnConnection, PKvsIpAddress pKvsIpAddress) +{ + PTurnPeer pTurnPeer = NULL, pCurrTurnPeer = NULL; + PDoubleListNode pCurNode = NULL; + UINT64 data; + + doubleListGetHeadNode(pTurnConnection->turnPeerList, &pCurNode); + while (pCurNode != NULL) { + doubleListGetNodeData(pCurNode, &data); + pCurNode = pCurNode->pNext; + + pCurrTurnPeer = (PTurnPeer) data; + if (isSameIpAddress(&pCurrTurnPeer->address, pKvsIpAddress, TRUE)) { + pTurnPeer = pCurrTurnPeer; + // Stop the loop iteration + pCurNode = NULL; + } + } + + return pTurnPeer; +} diff --git a/src/source/Ice/TurnConnection.h b/src/source/Ice/TurnConnection.h index c9823d4744..d5d4565fac 100644 --- a/src/source/Ice/TurnConnection.h +++ b/src/source/Ice/TurnConnection.h @@ -39,6 +39,7 @@ extern "C" { #define DEFAULT_TURN_MESSAGE_SEND_CHANNEL_DATA_BUFFER_LEN (10 * 1024) #define DEFAULT_TURN_MESSAGE_RECV_CHANNEL_DATA_BUFFER_LEN (10 * 1024) #define DEFAULT_TURN_CHANNEL_DATA_BUFFER_SIZE 128 +#define DEFAULT_TURN_MAX_PEER_COUNT 16 // all turn channel numbers must be greater than 0x4000 and less than 0x7FFF #define TURN_CHANNEL_BIND_CHANNEL_NUMBER_BASE (UINT16) 0x4000 @@ -206,6 +207,7 @@ STATUS turnConnectionHandleChannelData(PTurnConnection, PBYTE, UINT32, PTurnChan STATUS turnConnectionHandleChannelDataTcpMode(PTurnConnection, PBYTE, UINT32, PTurnChannelData, PUINT32); PTurnPeer turnConnectionGetPeerWithChannelNumber(PTurnConnection, UINT16); +PTurnPeer turnConnectionGetPeerWithIp(PTurnConnection, PKvsIpAddress); #ifdef __cplusplus } diff --git a/src/source/Stun/Stun.c b/src/source/Stun/Stun.c index ced4d4c4ef..aa5b536553 100644 --- a/src/source/Stun/Stun.c +++ b/src/source/Stun/Stun.c @@ -916,7 +916,7 @@ STATUS deserializeStunPacket(PBYTE pStunBuffer, UINT32 bufferSize, PBYTE passwor // Copy the padded error phrase MEMCPY(pStunAttributeErrorCode->errorPhrase, ((PBYTE) pStunAttributeHeader + STUN_ATTRIBUTE_HEADER_LEN + STUN_ERROR_CODE_PACKET_ERROR_PHRASE_OFFSET), - pStunAttributeErrorCode->paddedLength); + pStunAttributeErrorCode->paddedLength - STUN_ERROR_CODE_PACKET_ERROR_PHRASE_OFFSET); attributeSize = SIZEOF(StunAttributeErrorCode) + pStunAttributeErrorCode->paddedLength; break; diff --git a/tst/StunFunctionalityTest.cpp b/tst/StunFunctionalityTest.cpp index 8741a9754b..a19c09b5f9 100644 --- a/tst/StunFunctionalityTest.cpp +++ b/tst/StunFunctionalityTest.cpp @@ -574,6 +574,36 @@ TEST_F(StunFunctionalityTest, serializeDeserializeStunControlAttribute) EXPECT_EQ(STATUS_SUCCESS, freeStunPacket(&pDeserializedPacket)); } +TEST_F(StunFunctionalityTest, deserializeStunErrorCode) +{ + /** + * Error Code is 403. Error phrase is "Forbidden IP" without null terminator + */ + BYTE stunErrorBuffer[] = {0x01, 0x18, 0x00, 0x54, 0x21, 0x12, 0xa4, 0x42, 0xd8, 0x83, 0x1d, 0x97, 0x54, 0x68, 0xf1, + 0xbc, 0xf2, 0xe3, 0x40, 0x96, 0x00, 0x09, 0x00, 0x10, 0x00, 0x00, 0x04, 0x03, 0x46, 0x6f, + 0x72, 0x62, 0x69, 0x64, 0x64, 0x65, 0x6e, 0x20, 0x49, 0x50, 0x80, 0x22, 0x00, 0x1a, 0x43, + 0x6f, 0x74, 0x75, 0x72, 0x6e, 0x2d, 0x34, 0x2e, 0x35, 0x2e, 0x31, 0x2e, 0x31, 0x20, 0x27, + 0x64, 0x61, 0x6e, 0x20, 0x45, 0x69, 0x64, 0x65, 0x72, 0x27, 0x20, 0x27, 0x00, 0x08, 0x00, + 0x14, 0xba, 0xd4, 0xef, 0xe4, 0x0c, 0xa8, 0x6c, 0x6e, 0xc6, 0x10, 0xf1, 0x48, 0xaa, 0xc6, + 0x8f, 0xe9, 0xb6, 0x25, 0x58, 0xd6, 0x80, 0x28, 0x00, 0x04, 0x0d, 0xc7, 0xfe, 0x7a}; + BYTE turnKey[] = {0x69, 0xa8, 0xc7, 0xf7, 0x79, 0x72, 0x3c, 0x58, 0xae, 0xc4, 0xbd, 0xa3, 0x79, 0x1c, 0x02, 0xbd}; + PStunPacket pStunPacket = NULL; + PStunAttributeErrorCode pStunAttributeErrorCode = NULL; + PStunAttributeHeader pStunAttr = NULL; + + EXPECT_EQ(STATUS_SUCCESS, deserializeStunPacket(stunErrorBuffer, ARRAY_SIZE(stunErrorBuffer), turnKey, + MD5_DIGEST_LENGTH, &pStunPacket)); + + EXPECT_EQ(STATUS_SUCCESS, getStunAttribute(pStunPacket, STUN_ATTRIBUTE_TYPE_ERROR_CODE, &pStunAttr)); + EXPECT_TRUE(pStunAttr != NULL); + pStunAttributeErrorCode = (PStunAttributeErrorCode) pStunAttr; + + EXPECT_EQ(pStunAttributeErrorCode->errorCode, 403); + EXPECT_EQ(0, STRCMP(pStunAttributeErrorCode->errorPhrase, "Forbidden IP")); + + EXPECT_EQ(STATUS_SUCCESS, freeStunPacket(&pStunPacket)); +} + } } }