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;
}