STATUS turnConnectionStepState()

in src/source/Ice/TurnConnection.c [871:1110]


STATUS turnConnectionStepState(PTurnConnection pTurnConnection)
{
    ENTERS();
    STATUS retStatus = STATUS_SUCCESS;
    UINT32 readyPeerCount = 0, channelWithPermissionCount = 0;
    UINT64 currentTime = GETTIME();
    CHAR ipAddrStr[KVS_IP_ADDRESS_STRING_BUFFER_LEN];
    TURN_CONNECTION_STATE previousState = TURN_STATE_NEW;
    BOOL refreshPeerPermission = FALSE;
    UINT32 i = 0;

    CHK(pTurnConnection != NULL, STATUS_NULL_ARG);

    previousState = pTurnConnection->state;

    switch (pTurnConnection->state) {
        case TURN_STATE_NEW:
            // create empty turn allocation request
            CHK_STATUS(turnConnectionPackageTurnAllocationRequest(NULL, NULL, NULL, 0, DEFAULT_TURN_ALLOCATION_LIFETIME_SECONDS,
                                                                  &pTurnConnection->pTurnPacket));

            pTurnConnection->state = TURN_STATE_CHECK_SOCKET_CONNECTION;
            pTurnConnection->stateTimeoutTime = currentTime + DEFAULT_TURN_SOCKET_CONNECT_TIMEOUT;
            break;

        case TURN_STATE_CHECK_SOCKET_CONNECTION:
            if (socketConnectionIsConnected(pTurnConnection->pControlChannel)) {
                /* initialize TLS once tcp connection is established */
                /* Start receiving data for TLS handshake */
                ATOMIC_STORE_BOOL(&pTurnConnection->pControlChannel->receiveData, TRUE);

                /* We dont support DTLS and TCP, so only options are TCP/TLS and UDP. */
                /* TODO: add plain TCP once it becomes available. */
                if (pTurnConnection->protocol == KVS_SOCKET_PROTOCOL_TCP && pTurnConnection->pControlChannel->pTlsSession == NULL) {
                    CHK_STATUS(socketConnectionInitSecureConnection(pTurnConnection->pControlChannel, FALSE));
                }

                pTurnConnection->state = TURN_STATE_GET_CREDENTIALS;
                pTurnConnection->stateTimeoutTime = currentTime + DEFAULT_TURN_GET_CREDENTIAL_TIMEOUT;
            } else {
                CHK(currentTime < pTurnConnection->stateTimeoutTime, STATUS_TURN_CONNECTION_STATE_TRANSITION_TIMEOUT);
            }

        case TURN_STATE_GET_CREDENTIALS:

            if (pTurnConnection->credentialObtained) {
                DLOGV("Updated turn allocation request credential after receiving 401");

                // update turn allocation packet with credentials
                CHK_STATUS(freeStunPacket(&pTurnConnection->pTurnPacket));
                CHK_STATUS(turnConnectionGetLongTermKey(pTurnConnection->turnServer.username, pTurnConnection->turnRealm,
                                                        pTurnConnection->turnServer.credential, pTurnConnection->longTermKey,
                                                        SIZEOF(pTurnConnection->longTermKey)));
                CHK_STATUS(turnConnectionPackageTurnAllocationRequest(pTurnConnection->turnServer.username, pTurnConnection->turnRealm,
                                                                      pTurnConnection->turnNonce, pTurnConnection->nonceLen,
                                                                      DEFAULT_TURN_ALLOCATION_LIFETIME_SECONDS, &pTurnConnection->pTurnPacket));

                pTurnConnection->state = TURN_STATE_ALLOCATION;
                pTurnConnection->stateTimeoutTime = currentTime + DEFAULT_TURN_ALLOCATION_TIMEOUT;
            } else {
                CHK(currentTime < pTurnConnection->stateTimeoutTime, STATUS_TURN_CONNECTION_STATE_TRANSITION_TIMEOUT);
            }
            break;

        case TURN_STATE_ALLOCATION:

            if (ATOMIC_LOAD_BOOL(&pTurnConnection->hasAllocation)) {
                CHK_STATUS(getIpAddrStr(&pTurnConnection->relayAddress, ipAddrStr, ARRAY_SIZE(ipAddrStr)));
                DLOGD("Relay address received: %s, port: %u", ipAddrStr, (UINT16) getInt16(pTurnConnection->relayAddress.port));

                if (pTurnConnection->pTurnCreatePermissionPacket != NULL) {
                    CHK_STATUS(freeStunPacket(&pTurnConnection->pTurnCreatePermissionPacket));
                }
                CHK_STATUS(createStunPacket(STUN_PACKET_TYPE_CREATE_PERMISSION, NULL, &pTurnConnection->pTurnCreatePermissionPacket));
                // use host address as placeholder. hostAddress should have the same family as peer address
                CHK_STATUS(appendStunAddressAttribute(pTurnConnection->pTurnCreatePermissionPacket, STUN_ATTRIBUTE_TYPE_XOR_PEER_ADDRESS,
                                                      &pTurnConnection->hostAddress));
                CHK_STATUS(appendStunUsernameAttribute(pTurnConnection->pTurnCreatePermissionPacket, pTurnConnection->turnServer.username));
                CHK_STATUS(appendStunRealmAttribute(pTurnConnection->pTurnCreatePermissionPacket, pTurnConnection->turnRealm));
                CHK_STATUS(
                    appendStunNonceAttribute(pTurnConnection->pTurnCreatePermissionPacket, pTurnConnection->turnNonce, pTurnConnection->nonceLen));

                // create channel bind packet here too so for each peer as soon as permission is created, it can start
                // sending chaneel bind request
                if (pTurnConnection->pTurnChannelBindPacket != NULL) {
                    CHK_STATUS(freeStunPacket(&pTurnConnection->pTurnChannelBindPacket));
                }
                CHK_STATUS(createStunPacket(STUN_PACKET_TYPE_CHANNEL_BIND_REQUEST, NULL, &pTurnConnection->pTurnChannelBindPacket));
                // use host address as placeholder
                CHK_STATUS(appendStunAddressAttribute(pTurnConnection->pTurnChannelBindPacket, STUN_ATTRIBUTE_TYPE_XOR_PEER_ADDRESS,
                                                      &pTurnConnection->hostAddress));
                CHK_STATUS(appendStunChannelNumberAttribute(pTurnConnection->pTurnChannelBindPacket, 0));
                CHK_STATUS(appendStunUsernameAttribute(pTurnConnection->pTurnChannelBindPacket, pTurnConnection->turnServer.username));
                CHK_STATUS(appendStunRealmAttribute(pTurnConnection->pTurnChannelBindPacket, pTurnConnection->turnRealm));
                CHK_STATUS(appendStunNonceAttribute(pTurnConnection->pTurnChannelBindPacket, pTurnConnection->turnNonce, pTurnConnection->nonceLen));

                if (pTurnConnection->pTurnAllocationRefreshPacket != NULL) {
                    CHK_STATUS(freeStunPacket(&pTurnConnection->pTurnAllocationRefreshPacket));
                }
                CHK_STATUS(createStunPacket(STUN_PACKET_TYPE_REFRESH, NULL, &pTurnConnection->pTurnAllocationRefreshPacket));
                CHK_STATUS(appendStunLifetimeAttribute(pTurnConnection->pTurnAllocationRefreshPacket, DEFAULT_TURN_ALLOCATION_LIFETIME_SECONDS));
                CHK_STATUS(appendStunUsernameAttribute(pTurnConnection->pTurnAllocationRefreshPacket, pTurnConnection->turnServer.username));
                CHK_STATUS(appendStunRealmAttribute(pTurnConnection->pTurnAllocationRefreshPacket, pTurnConnection->turnRealm));
                CHK_STATUS(
                    appendStunNonceAttribute(pTurnConnection->pTurnAllocationRefreshPacket, pTurnConnection->turnNonce, pTurnConnection->nonceLen));

                pTurnConnection->state = TURN_STATE_CREATE_PERMISSION;
                pTurnConnection->stateTimeoutTime = currentTime + DEFAULT_TURN_CREATE_PERMISSION_TIMEOUT;

            } else {
                CHK(currentTime < pTurnConnection->stateTimeoutTime, STATUS_TURN_CONNECTION_STATE_TRANSITION_TIMEOUT);
            }
            break;

        case TURN_STATE_CREATE_PERMISSION:

            for (i = 0; i < pTurnConnection->turnPeerCount; ++i) {
                // As soon as create permission succeeded, we start sending channel bind message.
                // So connectionState could've already advanced to ready state.
                if (pTurnConnection->turnPeerList[i].connectionState == TURN_PEER_CONN_STATE_BIND_CHANNEL ||
                    pTurnConnection->turnPeerList[i].connectionState == TURN_PEER_CONN_STATE_READY) {
                    channelWithPermissionCount++;
                }
            }

            // push back timeout if no peer is available yet
            if (pTurnConnection->turnPeerCount == 0) {
                pTurnConnection->stateTimeoutTime = currentTime + DEFAULT_TURN_CREATE_PERMISSION_TIMEOUT;
                CHK(FALSE, retStatus);
            }

            if (currentTime >= pTurnConnection->stateTimeoutTime) {
                CHK(channelWithPermissionCount > 0, STATUS_TURN_CONNECTION_FAILED_TO_CREATE_PERMISSION);

                // go to next state if we have at least one ready peer
                pTurnConnection->state = TURN_STATE_BIND_CHANNEL;
                pTurnConnection->stateTimeoutTime = currentTime + DEFAULT_TURN_BIND_CHANNEL_TIMEOUT;
            }
            break;

        case TURN_STATE_BIND_CHANNEL:

            for (i = 0; i < pTurnConnection->turnPeerCount; ++i) {
                if (pTurnConnection->turnPeerList[i].connectionState == TURN_PEER_CONN_STATE_READY) {
                    readyPeerCount++;
                }
            }

            if (currentTime >= pTurnConnection->stateTimeoutTime || readyPeerCount == pTurnConnection->turnPeerCount) {
                CHK(readyPeerCount > 0, STATUS_TURN_CONNECTION_FAILED_TO_BIND_CHANNEL);
                // go to next state if we have at least one ready peer
                pTurnConnection->state = TURN_STATE_READY;
            }
            break;

        case TURN_STATE_READY:

            CHK_STATUS(turnConnectionRefreshPermission(pTurnConnection, &refreshPeerPermission));
            if (refreshPeerPermission) {
                // reset pTurnPeer->connectionState to make them go through create permission and channel bind again
                for (i = 0; i < pTurnConnection->turnPeerCount; ++i) {
                    pTurnConnection->turnPeerList[i].connectionState = TURN_PEER_CONN_STATE_CREATE_PERMISSION;
                }

                pTurnConnection->currentTimerCallingPeriod = DEFAULT_TURN_TIMER_INTERVAL_BEFORE_READY;
                CHK_STATUS(timerQueueUpdateTimerPeriod(pTurnConnection->timerQueueHandle, (UINT64) pTurnConnection,
                                                       (UINT32) ATOMIC_LOAD(&pTurnConnection->timerCallbackId),
                                                       pTurnConnection->currentTimerCallingPeriod));
                pTurnConnection->state = TURN_STATE_CREATE_PERMISSION;
                pTurnConnection->stateTimeoutTime = currentTime + DEFAULT_TURN_CREATE_PERMISSION_TIMEOUT;
            } else if (pTurnConnection->currentTimerCallingPeriod != DEFAULT_TURN_TIMER_INTERVAL_AFTER_READY) {
                // use longer timer interval as now it just needs to check disconnection and permission expiration.
                pTurnConnection->currentTimerCallingPeriod = DEFAULT_TURN_TIMER_INTERVAL_AFTER_READY;
                CHK_STATUS(timerQueueUpdateTimerPeriod(pTurnConnection->timerQueueHandle, (UINT64) pTurnConnection,
                                                       (UINT32) ATOMIC_LOAD(&pTurnConnection->timerCallbackId),
                                                       pTurnConnection->currentTimerCallingPeriod));
            }

            break;

        case TURN_STATE_CLEAN_UP:
            /* start cleanning up even if we dont receive allocation freed response in time, or if connection is already closed,
             * since we already sent multiple STUN refresh packets with 0 lifetime. */
            if (socketConnectionIsClosed(pTurnConnection->pControlChannel) || !ATOMIC_LOAD_BOOL(&pTurnConnection->hasAllocation) ||
                currentTime >= pTurnConnection->stateTimeoutTime) {
                // clean transactionId store for each turn peer, preserving the peers
                for (i = 0; i < pTurnConnection->turnPeerCount; ++i) {
                    transactionIdStoreClear(pTurnConnection->turnPeerList[i].pTransactionIdStore);
                }

                CHK_STATUS(turnConnectionFreePreAllocatedPackets(pTurnConnection));
                CHK_STATUS(socketConnectionClosed(pTurnConnection->pControlChannel));
                pTurnConnection->state = STATUS_SUCCEEDED(pTurnConnection->errorStatus) ? TURN_STATE_NEW : TURN_STATE_FAILED;
                ATOMIC_STORE_BOOL(&pTurnConnection->shutdownComplete, TRUE);
            }

            break;

        case TURN_STATE_FAILED:
            DLOGW("TurnConnection in TURN_STATE_FAILED due to 0x%08x. Aborting TurnConnection", pTurnConnection->errorStatus);
            /* Since we are aborting, not gonna do cleanup */
            ATOMIC_STORE_BOOL(&pTurnConnection->hasAllocation, FALSE);
            /* If we haven't done cleanup, go to cleanup state which will do the cleanup then go to failed state again. */
            if (!ATOMIC_LOAD_BOOL(&pTurnConnection->shutdownComplete)) {
                pTurnConnection->state = TURN_STATE_CLEAN_UP;
                pTurnConnection->stateTimeoutTime = currentTime + DEFAULT_TURN_CLEAN_UP_TIMEOUT;
            }

            break;

        default:
            break;
    }

CleanUp:

    CHK_LOG_ERR(retStatus);

    if (STATUS_SUCCEEDED(retStatus) && ATOMIC_LOAD_BOOL(&pTurnConnection->stopTurnConnection) && pTurnConnection->state != TURN_STATE_CLEAN_UP &&
        pTurnConnection->state != TURN_STATE_NEW) {
        pTurnConnection->state = TURN_STATE_CLEAN_UP;
        pTurnConnection->stateTimeoutTime = currentTime + DEFAULT_TURN_CLEAN_UP_TIMEOUT;
    }

    /* move to failed state if retStatus is failed status and state is not yet TURN_STATE_FAILED */
    if (STATUS_FAILED(retStatus) && pTurnConnection->state != TURN_STATE_FAILED) {
        pTurnConnection->errorStatus = retStatus;
        pTurnConnection->state = TURN_STATE_FAILED;
        /* fix up state to trigger transition into TURN_STATE_FAILED  */
        retStatus = STATUS_SUCCESS;
    }

    if (pTurnConnection != NULL && previousState != pTurnConnection->state) {
        DLOGD("TurnConnection state changed from %s to %s", turnConnectionGetStateStr(previousState),
              turnConnectionGetStateStr(pTurnConnection->state));
    }

    LEAVES();
    return retStatus;
}