STATUS turnConnectionHandleStun()

in src/source/Ice/TurnConnection.c [192:332]


STATUS turnConnectionHandleStun(PTurnConnection pTurnConnection, PBYTE pBuffer, UINT32 bufferLen)
{
    ENTERS();
    STATUS retStatus = STATUS_SUCCESS;
    UINT16 stunPacketType = 0;
    PStunAttributeHeader pStunAttr = NULL;
    PStunAttributeAddress pStunAttributeAddress = NULL;
    PStunAttributeLifetime pStunAttributeLifetime = NULL;
    PStunPacket pStunPacket = NULL;
    CHAR ipAddrStr[KVS_IP_ADDRESS_STRING_BUFFER_LEN];
    BOOL locked = FALSE;
    ATOMIC_BOOL hasAllocation = FALSE;
    PTurnPeer pTurnPeer = NULL;
    UINT64 currentTime;
    UINT32 i;

    CHK(pTurnConnection != NULL, STATUS_NULL_ARG);
    CHK(pBuffer != NULL && bufferLen > 0, STATUS_INVALID_ARG);
    CHK(IS_STUN_PACKET(pBuffer) && !STUN_PACKET_IS_TYPE_ERROR(pBuffer), retStatus);

    MUTEX_LOCK(pTurnConnection->lock);
    locked = TRUE;

    currentTime = GETTIME();
    // only handling STUN response
    stunPacketType = (UINT16) getInt16(*((PUINT16) pBuffer));

    switch (stunPacketType) {
        case STUN_PACKET_TYPE_ALLOCATE_SUCCESS_RESPONSE:
            /* If shutdown has been initiated, ignore the allocation response */
            CHK(!ATOMIC_LOAD(&pTurnConnection->stopTurnConnection), retStatus);
            CHK_STATUS(deserializeStunPacket(pBuffer, bufferLen, pTurnConnection->longTermKey, KVS_MD5_DIGEST_LENGTH, &pStunPacket));
            CHK_STATUS(getStunAttribute(pStunPacket, STUN_ATTRIBUTE_TYPE_XOR_RELAYED_ADDRESS, &pStunAttr));
            CHK_WARN(pStunAttr != NULL, retStatus, "No relay address attribute found in TURN allocate response. Dropping Packet");
            CHK_STATUS(getStunAttribute(pStunPacket, STUN_ATTRIBUTE_TYPE_LIFETIME, (PStunAttributeHeader*) &pStunAttributeLifetime));
            CHK_WARN(pStunAttributeLifetime != NULL, retStatus, "Missing lifetime in Allocation response. Dropping Packet");

            // convert lifetime to 100ns and store it
            pTurnConnection->allocationExpirationTime = (pStunAttributeLifetime->lifetime * HUNDREDS_OF_NANOS_IN_A_SECOND) + currentTime;
            DLOGD("TURN Allocation succeeded. Life time: %u seconds. Allocation expiration epoch %" PRIu64, pStunAttributeLifetime->lifetime,
                  pTurnConnection->allocationExpirationTime / DEFAULT_TIME_UNIT_IN_NANOS);

            pStunAttributeAddress = (PStunAttributeAddress) pStunAttr;
            pTurnConnection->relayAddress = pStunAttributeAddress->address;
            ATOMIC_STORE_BOOL(&pTurnConnection->hasAllocation, TRUE);

            if (!pTurnConnection->relayAddressReported && pTurnConnection->turnConnectionCallbacks.relayAddressAvailableFn != NULL) {
                pTurnConnection->relayAddressReported = TRUE;

                // release lock early and report relay candidate
                MUTEX_UNLOCK(pTurnConnection->lock);
                locked = FALSE;

                pTurnConnection->turnConnectionCallbacks.relayAddressAvailableFn(pTurnConnection->turnConnectionCallbacks.customData,
                                                                                 &pTurnConnection->relayAddress, pTurnConnection->pControlChannel);
            }

            break;

        case STUN_PACKET_TYPE_REFRESH_SUCCESS_RESPONSE:
            CHK_STATUS(deserializeStunPacket(pBuffer, bufferLen, pTurnConnection->longTermKey, KVS_MD5_DIGEST_LENGTH, &pStunPacket));
            CHK_STATUS(getStunAttribute(pStunPacket, STUN_ATTRIBUTE_TYPE_LIFETIME, (PStunAttributeHeader*) &pStunAttributeLifetime));
            CHK_WARN(pStunAttributeLifetime != NULL, retStatus, "No lifetime attribute found in TURN refresh response. Dropping Packet");

            if (pStunAttributeLifetime->lifetime == 0) {
                hasAllocation = ATOMIC_EXCHANGE_BOOL(&pTurnConnection->hasAllocation, FALSE);
                CHK(hasAllocation, retStatus);
                DLOGD("TURN Allocation freed.");
                CVAR_SIGNAL(pTurnConnection->freeAllocationCvar);
            } else {
                // convert lifetime to 100ns and store it
                pTurnConnection->allocationExpirationTime = (pStunAttributeLifetime->lifetime * HUNDREDS_OF_NANOS_IN_A_SECOND) + currentTime;
                DLOGD("Refreshed TURN allocation lifetime is %u seconds. Allocation expiration epoch %" PRIu64, pStunAttributeLifetime->lifetime,
                      pTurnConnection->allocationExpirationTime / DEFAULT_TIME_UNIT_IN_NANOS);
            }

            break;

        case STUN_PACKET_TYPE_CREATE_PERMISSION_SUCCESS_RESPONSE:
            for (i = 0; i < pTurnConnection->turnPeerCount; ++i) {
                pTurnPeer = &pTurnConnection->turnPeerList[i];
                if (transactionIdStoreHasId(pTurnPeer->pTransactionIdStore, pBuffer + STUN_PACKET_TRANSACTION_ID_OFFSET)) {
                    if (pTurnPeer->connectionState == TURN_PEER_CONN_STATE_CREATE_PERMISSION) {
                        pTurnPeer->connectionState = TURN_PEER_CONN_STATE_BIND_CHANNEL;
                        CHK_STATUS(getIpAddrStr(&pTurnPeer->address, ipAddrStr, ARRAY_SIZE(ipAddrStr)));
                        DLOGD("create permission succeeded for peer %s", ipAddrStr);
                    }

                    pTurnPeer->permissionExpirationTime = TURN_PERMISSION_LIFETIME + currentTime;
                }
            }

            break;

        case STUN_PACKET_TYPE_CHANNEL_BIND_SUCCESS_RESPONSE:
            for (i = 0; i < pTurnConnection->turnPeerCount; ++i) {
                pTurnPeer = &pTurnConnection->turnPeerList[i];
                if (pTurnPeer->connectionState == TURN_PEER_CONN_STATE_BIND_CHANNEL &&
                    transactionIdStoreHasId(pTurnPeer->pTransactionIdStore, pBuffer + STUN_PACKET_TRANSACTION_ID_OFFSET)) {
                    // pTurnPeer->ready means this peer is ready to receive data. pTurnPeer->connectionState could
                    // change after reaching TURN_PEER_CONN_STATE_READY due to refreshing permission and channel.
                    if (!pTurnPeer->ready) {
                        pTurnPeer->ready = TRUE;
                    }
                    pTurnPeer->connectionState = TURN_PEER_CONN_STATE_READY;

                    CHK_STATUS(getIpAddrStr(&pTurnPeer->address, ipAddrStr, ARRAY_SIZE(ipAddrStr)));
                    DLOGD("Channel bind succeeded with peer %s, port: %u, channel number %u", ipAddrStr, (UINT16) getInt16(pTurnPeer->address.port),
                          pTurnPeer->channelNumber);

                    break;
                }
            }

            break;

        case STUN_PACKET_TYPE_DATA_INDICATION:
            DLOGD("Received data indication");
            // no-ops for now, turn server has to use data channel if it is established. client is free to use either.
            break;

        default:
            DLOGD("Drop unsupported TURN message with type 0x%02x", stunPacketType);
            break;
    }

CleanUp:

    CHK_LOG_ERR(retStatus);

    if (locked) {
        MUTEX_UNLOCK(pTurnConnection->lock);
    }

    if (pStunPacket != NULL) {
        freeStunPacket(&pStunPacket);
    }

    LEAVES();
    return retStatus;
}