#define MS_CLASS "RTC::IceServer"
#include <utility>
#include "Logger.hpp"
#include "RTC/IceServer.hpp"
namespace RTC
{
static constexpr size_t StunSerializeBufferSize{ 65536 };
thread_local static uint8_t StunSerializeBuffer[StunSerializeBufferSize];
static constexpr size_t MaxTuples{ 8 };
IceServer::IceServer(Listener* listener, const std::string& usernameFragment, const std::string& password)
: listener(listener), usernameFragment(usernameFragment), password(password)
{
MS_TRACE();
this->listener->OnIceServerLocalUsernameFragmentAdded(this, usernameFragment);
}
IceServer::~IceServer()
{
MS_TRACE();
this->listener->OnIceServerLocalUsernameFragmentRemoved(this, usernameFragment);
if (!this->oldUsernameFragment.empty())
{
this->listener->OnIceServerLocalUsernameFragmentRemoved(this, this->oldUsernameFragment);
}
for (const auto& it : this->tuples)
{
auto* storedTuple = const_cast<RTC::TransportTuple*>(std::addressof(it));
this->listener->OnIceServerTupleRemoved(this, storedTuple);
}
}
void IceServer::ProcessStunPacket(RTC::StunPacket* packet, RTC::TransportTuple* tuple)
{
MS_TRACE();
if (packet->GetMethod() != RTC::StunPacket::Method::BINDING)
{
if (packet->GetClass() == RTC::StunPacket::Class::REQUEST)
{
MS_WARN_TAG(
ice,
"unknown method %#.3x in STUN Request => 400",
static_cast<unsigned int>(packet->GetMethod()));
RTC::StunPacket* response = packet->CreateErrorResponse(400);
response->Serialize(StunSerializeBuffer);
this->listener->OnIceServerSendStunPacket(this, response, tuple);
delete response;
}
else
{
MS_WARN_TAG(
ice,
"ignoring STUN Indication or Response with unknown method %#.3x",
static_cast<unsigned int>(packet->GetMethod()));
}
return;
}
if (!packet->HasFingerprint() && packet->GetClass() != RTC::StunPacket::Class::INDICATION)
{
if (packet->GetClass() == RTC::StunPacket::Class::REQUEST)
{
MS_WARN_TAG(ice, "STUN Binding Request without FINGERPRINT => 400");
RTC::StunPacket* response = packet->CreateErrorResponse(400);
response->Serialize(StunSerializeBuffer);
this->listener->OnIceServerSendStunPacket(this, response, tuple);
delete response;
}
else
{
MS_WARN_TAG(ice, "ignoring STUN Binding Response without FINGERPRINT");
}
return;
}
switch (packet->GetClass())
{
case RTC::StunPacket::Class::REQUEST:
{
if (!packet->HasMessageIntegrity() || (packet->GetPriority() == 0u) || packet->GetUsername().empty())
{
MS_WARN_TAG(ice, "mising required attributes in STUN Binding Request => 400");
RTC::StunPacket* response = packet->CreateErrorResponse(400);
response->Serialize(StunSerializeBuffer);
this->listener->OnIceServerSendStunPacket(this, response, tuple);
delete response;
return;
}
switch (packet->CheckAuthentication(this->usernameFragment, this->password))
{
case RTC::StunPacket::Authentication::OK:
{
if (!this->oldUsernameFragment.empty() && !this->oldPassword.empty())
{
MS_DEBUG_TAG(ice, "new ICE credentials applied");
this->listener->OnIceServerLocalUsernameFragmentRemoved(this, this->oldUsernameFragment);
this->oldUsernameFragment.clear();
this->oldPassword.clear();
}
break;
}
case RTC::StunPacket::Authentication::UNAUTHORIZED:
{
if (
!this->oldUsernameFragment.empty() &&
!this->oldPassword.empty() &&
packet->CheckAuthentication(this->oldUsernameFragment, this->oldPassword) == RTC::StunPacket::Authentication::OK
)
{
MS_DEBUG_TAG(ice, "using old ICE credentials");
break;
}
MS_WARN_TAG(ice, "wrong authentication in STUN Binding Request => 401");
RTC::StunPacket* response = packet->CreateErrorResponse(401);
response->Serialize(StunSerializeBuffer);
this->listener->OnIceServerSendStunPacket(this, response, tuple);
delete response;
return;
}
case RTC::StunPacket::Authentication::BAD_REQUEST:
{
MS_WARN_TAG(ice, "cannot check authentication in STUN Binding Request => 400");
RTC::StunPacket* response = packet->CreateErrorResponse(400);
response->Serialize(StunSerializeBuffer);
this->listener->OnIceServerSendStunPacket(this, response, tuple);
delete response;
return;
}
}
if (packet->GetIceControlled())
{
MS_WARN_TAG(ice, "peer indicates ICE-CONTROLLED in STUN Binding Request => 487");
RTC::StunPacket* response = packet->CreateErrorResponse(487);
response->Serialize(StunSerializeBuffer);
this->listener->OnIceServerSendStunPacket(this, response, tuple);
delete response;
return;
}
MS_DEBUG_DEV(
"processing STUN Binding Request [Priority:%" PRIu32 ", UseCandidate:%s]",
static_cast<uint32_t>(packet->GetPriority()),
packet->HasUseCandidate() ? "true" : "false");
RTC::StunPacket* response = packet->CreateSuccessResponse();
response->SetXorMappedAddress(tuple->GetRemoteAddress());
if (this->oldPassword.empty())
response->Authenticate(this->password);
else
response->Authenticate(this->oldPassword);
response->Serialize(StunSerializeBuffer);
this->listener->OnIceServerSendStunPacket(this, response, tuple);
delete response;
uint32_t nomination{ 0u };
if (packet->HasNomination())
nomination = packet->GetNomination();
HandleTuple(tuple, packet->HasUseCandidate(), packet->HasNomination(), nomination);
break;
}
case RTC::StunPacket::Class::INDICATION:
{
MS_DEBUG_TAG(ice, "STUN Binding Indication processed");
break;
}
case RTC::StunPacket::Class::SUCCESS_RESPONSE:
{
MS_DEBUG_TAG(ice, "STUN Binding Success Response processed");
break;
}
case RTC::StunPacket::Class::ERROR_RESPONSE:
{
MS_DEBUG_TAG(ice, "STUN Binding Error Response processed");
break;
}
}
}
bool IceServer::IsValidTuple(const RTC::TransportTuple* tuple) const
{
MS_TRACE();
return HasTuple(tuple) != nullptr;
}
void IceServer::RemoveTuple(RTC::TransportTuple* tuple)
{
MS_TRACE();
RTC::TransportTuple* removedTuple{ nullptr };
auto it = this->tuples.begin();
for (; it != this->tuples.end(); ++it)
{
RTC::TransportTuple* storedTuple = std::addressof(*it);
if (storedTuple->Compare(tuple))
{
removedTuple = storedTuple;
break;
}
}
if (!removedTuple)
return;
this->tuples.erase(it);
if (removedTuple == this->selectedTuple)
{
this->selectedTuple = nullptr;
if (this->tuples.begin() != this->tuples.end())
{
SetSelectedTuple(std::addressof(*this->tuples.begin()));
}
else
{
this->state = IceState::DISCONNECTED;
this->listener->OnIceServerDisconnected(this);
}
}
this->listener->OnIceServerTupleRemoved(this, removedTuple);
}
void IceServer::ForceSelectedTuple(const RTC::TransportTuple* tuple)
{
MS_TRACE();
MS_ASSERT(
this->selectedTuple, "cannot force the selected tuple if there was not a selected tuple");
auto* storedTuple = HasTuple(tuple);
MS_ASSERT(
storedTuple,
"cannot force the selected tuple if the given tuple was not already a valid tuple");
SetSelectedTuple(storedTuple);
}
void IceServer::HandleTuple(
RTC::TransportTuple* tuple, bool hasUseCandidate, bool hasNomination, uint32_t nomination)
{
MS_TRACE();
switch (this->state)
{
case IceState::NEW:
{
MS_ASSERT(
this->tuples.empty(), "state is 'new' but there are %zu tuples", this->tuples.size());
MS_ASSERT(!this->selectedTuple, "state is 'new' but there is selected tuple");
if (!hasUseCandidate && !hasNomination)
{
MS_DEBUG_TAG(
ice,
"transition from state 'new' to 'connected' [hasUseCandidate:%s, hasNomination:%s, nomination:%" PRIu32
"]",
hasUseCandidate ? "true" : "false",
hasNomination ? "true" : "false",
nomination);
auto* storedTuple = AddTuple(tuple);
SetSelectedTuple(storedTuple);
this->state = IceState::CONNECTED;
this->listener->OnIceServerConnected(this);
}
else
{
auto* storedTuple = AddTuple(tuple);
if ((hasNomination && nomination > this->remoteNomination) || !hasNomination)
{
MS_DEBUG_TAG(
ice,
"transition from state 'new' to 'completed' [hasUseCandidate:%s, hasNomination:%s, nomination:%" PRIu32
"]",
hasUseCandidate ? "true" : "false",
hasNomination ? "true" : "false",
nomination);
SetSelectedTuple(storedTuple);
this->state = IceState::COMPLETED;
if (hasNomination && nomination > this->remoteNomination)
this->remoteNomination = nomination;
this->listener->OnIceServerCompleted(this);
}
}
break;
}
case IceState::DISCONNECTED:
{
MS_ASSERT(
this->tuples.empty(),
"state is 'disconnected' but there are %zu tuples",
this->tuples.size());
MS_ASSERT(!this->selectedTuple, "state is 'disconnected' but there is selected tuple");
if (!hasUseCandidate && !hasNomination)
{
MS_DEBUG_TAG(
ice,
"transition from state 'disconnected' to 'connected' [hasUseCandidate:%s, hasNomination:%s, nomination:%" PRIu32
"]",
hasUseCandidate ? "true" : "false",
hasNomination ? "true" : "false",
nomination);
auto* storedTuple = AddTuple(tuple);
SetSelectedTuple(storedTuple);
this->state = IceState::CONNECTED;
this->listener->OnIceServerConnected(this);
}
else
{
auto* storedTuple = AddTuple(tuple);
if ((hasNomination && nomination > this->remoteNomination) || !hasNomination)
{
MS_DEBUG_TAG(
ice,
"transition from state 'disconnected' to 'completed' [hasUseCandidate:%s, hasNomination:%s, nomination:%" PRIu32
"]",
hasUseCandidate ? "true" : "false",
hasNomination ? "true" : "false",
nomination);
SetSelectedTuple(storedTuple);
this->state = IceState::COMPLETED;
if (hasNomination && nomination > this->remoteNomination)
this->remoteNomination = nomination;
this->listener->OnIceServerCompleted(this);
}
}
break;
}
case IceState::CONNECTED:
{
MS_ASSERT(!this->tuples.empty(), "state is 'connected' but there are no tuples");
MS_ASSERT(this->selectedTuple, "state is 'connected' but there is not selected tuple");
if (!hasUseCandidate && !hasNomination)
{
if (!HasTuple(tuple))
AddTuple(tuple);
}
else
{
MS_DEBUG_TAG(
ice,
"transition from state 'connected' to 'completed' [hasUseCandidate:%s, hasNomination:%s, nomination:%" PRIu32
"]",
hasUseCandidate ? "true" : "false",
hasNomination ? "true" : "false",
nomination);
auto* storedTuple = HasTuple(tuple);
if (!storedTuple)
storedTuple = AddTuple(tuple);
if ((hasNomination && nomination > this->remoteNomination) || !hasNomination)
{
SetSelectedTuple(storedTuple);
this->state = IceState::COMPLETED;
if (hasNomination && nomination > this->remoteNomination)
this->remoteNomination = nomination;
this->listener->OnIceServerCompleted(this);
}
}
break;
}
case IceState::COMPLETED:
{
MS_ASSERT(!this->tuples.empty(), "state is 'completed' but there are no tuples");
MS_ASSERT(this->selectedTuple, "state is 'completed' but there is not selected tuple");
if (!hasUseCandidate && !hasNomination)
{
if (!HasTuple(tuple))
AddTuple(tuple);
}
else
{
auto* storedTuple = HasTuple(tuple);
if (!storedTuple)
storedTuple = AddTuple(tuple);
if ((hasNomination && nomination > this->remoteNomination) || !hasNomination)
{
SetSelectedTuple(storedTuple);
if (hasNomination && nomination > this->remoteNomination)
this->remoteNomination = nomination;
}
}
break;
}
}
}
inline RTC::TransportTuple* IceServer::AddTuple(RTC::TransportTuple* tuple)
{
MS_TRACE();
this->tuples.push_front(*tuple);
auto* storedTuple = std::addressof(*this->tuples.begin());
if (storedTuple->GetProtocol() == TransportTuple::Protocol::UDP)
storedTuple->StoreUdpRemoteAddress();
this->listener->OnIceServerTupleAdded(this, storedTuple);
if (this->tuples.size() > MaxTuples)
{
MS_WARN_TAG(ice, "too too many tuples, removing the oldest non selected one");
RTC::TransportTuple* removedTuple{ nullptr };
auto it = this->tuples.rbegin();
for (; it != this->tuples.rend(); ++it)
{
RTC::TransportTuple* otherStoredTuple = std::addressof(*it);
if (otherStoredTuple != storedTuple && otherStoredTuple != this->selectedTuple)
{
removedTuple = otherStoredTuple;
break;
}
}
MS_ASSERT(removedTuple, "couldn't find any tuple to be removed");
this->tuples.erase(std::next(it).base());
this->listener->OnIceServerTupleRemoved(this, removedTuple);
}
return storedTuple;
}
inline RTC::TransportTuple* IceServer::HasTuple(const RTC::TransportTuple* tuple) const
{
MS_TRACE();
if (!this->selectedTuple)
return nullptr;
if (this->selectedTuple->Compare(tuple))
return this->selectedTuple;
for (const auto& it : this->tuples)
{
auto* storedTuple = const_cast<RTC::TransportTuple*>(std::addressof(it));
if (storedTuple->Compare(tuple))
return storedTuple;
}
return nullptr;
}
inline void IceServer::SetSelectedTuple(RTC::TransportTuple* storedTuple)
{
MS_TRACE();
if (storedTuple == this->selectedTuple)
return;
this->selectedTuple = storedTuple;
this->listener->OnIceServerSelectedTuple(this, this->selectedTuple);
}
}