#define MS_CLASS "RTC::SCTP::Association"
#include "RTC/SCTP/association/Association.hpp"
#include "Logger.hpp"
#include "RTC/SCTP/packet/errorCauses/CookieReceivedWhileShuttingDownErrorCause.hpp"
#include "RTC/SCTP/packet/errorCauses/NoUserDataErrorCause.hpp"
#include "RTC/SCTP/packet/errorCauses/OutOfResourceErrorCause.hpp"
#include "RTC/SCTP/packet/errorCauses/ProtocolViolationErrorCause.hpp"
#include "RTC/SCTP/packet/errorCauses/UnrecognizedChunkTypeErrorCause.hpp"
#include "RTC/SCTP/packet/errorCauses/UserInitiatedAbortErrorCause.hpp"
#include "RTC/SCTP/packet/parameters/ForwardTsnSupportedParameter.hpp"
#include "RTC/SCTP/packet/parameters/StateCookieParameter.hpp"
#include "RTC/SCTP/packet/parameters/SupportedExtensionsParameter.hpp"
#include "RTC/SCTP/packet/parameters/ZeroChecksumAcceptableParameter.hpp"
#include "Utils.hpp"
#include <limits>
#include <sstream>
#include <string>
#include <type_traits>
namespace RTC
{
namespace SCTP
{
alignas(4) static thread_local uint8_t PacketFactoryBuffer[65536];
constexpr uint32_t MinVerificationTag{ 1 };
constexpr uint32_t MaxVerificationTag{ std::numeric_limits<uint32_t>::max() };
constexpr uint32_t MinInitialTsn{ 0 };
constexpr uint32_t MaxInitialTsn{ std::numeric_limits<uint32_t>::max() };
constexpr uint64_t MaxTieTag{ std::numeric_limits<uint64_t>::max() };
Association::Association(
const SctpOptions& sctpOptions,
AssociationListenerInterface* listener,
SharedInterface* shared,
bool isDataChannel)
: sctpOptions(sctpOptions),
associationListenerDeferrer(listener),
shared(shared),
packetSender(this, this->associationListenerDeferrer),
sendQueue(
this->associationListenerDeferrer,
sctpOptions.mtu,
sctpOptions.defaultStreamPriority,
sctpOptions.totalBufferedAmountLowThreshold),
t1InitTimer(this->shared->CreateBackoffTimer(
BackoffTimerHandleInterface::BackoffTimerHandleOptions{
.listener = this,
.label = "sctp-t1-init",
.baseTimeoutMs = sctpOptions.t1InitTimeoutMs,
.backoffAlgorithm = BackoffTimerHandleInterface::BackoffAlgorithm::EXPONENTIAL,
.maxBackoffTimeoutMs = sctpOptions.timerMaxBackoffTimeoutMs,
.maxRestarts = sctpOptions.maxInitRetransmissions,
})),
t1CookieTimer(this->shared->CreateBackoffTimer(
BackoffTimerHandleInterface::BackoffTimerHandleOptions{
.listener = this,
.label = "sctp-t1-cookie",
.baseTimeoutMs = sctpOptions.t1CookieTimeoutMs,
.backoffAlgorithm = BackoffTimerHandleInterface::BackoffAlgorithm::EXPONENTIAL,
.maxBackoffTimeoutMs = sctpOptions.timerMaxBackoffTimeoutMs,
.maxRestarts = sctpOptions.maxInitRetransmissions })),
t2ShutdownTimer(this->shared->CreateBackoffTimer(
BackoffTimerHandleInterface::BackoffTimerHandleOptions{
.listener = this,
.label = "sctp-t2-shutdown",
.baseTimeoutMs = sctpOptions.t2ShutdownTimeoutMs,
.backoffAlgorithm = BackoffTimerHandleInterface::BackoffAlgorithm::EXPONENTIAL,
.maxBackoffTimeoutMs = sctpOptions.timerMaxBackoffTimeoutMs,
.maxRestarts = sctpOptions.maxRetransmissions })),
maxPacketLength(Utils::Byte::PadDownTo4Bytes(this->sctpOptions.mtu)),
isDataChannel(isDataChannel)
{
MS_TRACE();
}
Association::~Association()
{
MS_TRACE();
}
void Association::Dump(int indentation) const
{
MS_TRACE();
const auto stateStringView = Association::StateToString(this->state);
const auto associationStateStringView = Types::AssociationStateToString(GetAssociationState());
MS_DUMP_CLEAN(indentation, "<SCTP::Association>");
MS_DUMP_CLEAN(
indentation,
" association state: %.*s (internal state: %.*s)",
static_cast<int>(associationStateStringView.size()),
associationStateStringView.data(),
static_cast<int>(stateStringView.size()),
stateStringView.data());
this->sctpOptions.Dump();
if (this->tcb)
{
this->tcb->Dump(indentation + 1);
}
const auto metrics = MakeMetrics();
if (metrics.has_value())
{
metrics->Dump(indentation + 1);
}
MS_DUMP_CLEAN(indentation, "</SCTP::Association>");
}
flatbuffers::Offset<FBS::SctpParameters::SctpParameters> Association::FillBuffer(
flatbuffers::FlatBufferBuilder& builder) const
{
MS_TRACE();
return FBS::SctpParameters::CreateSctpParameters(
builder,
this->sctpOptions.sourcePort,
this->sctpOptions.maxSendMessageSize,
this->sctpOptions.maxReceiveMessageSize,
this->sctpOptions.maxSendBufferSize,
this->sctpOptions.perStreamSendQueueLimit,
this->sctpOptions.maxReceiverWindowBufferSize,
this->isDataChannel);
}
Types::AssociationState Association::GetAssociationState() const
{
MS_TRACE();
switch (this->state)
{
case State::NEW:
{
return Types::AssociationState::NEW;
}
case State::CLOSED:
{
return Types::AssociationState::CLOSED;
}
case State::COOKIE_WAIT:
case State::COOKIE_ECHOED:
{
return Types::AssociationState::CONNECTING;
}
case State::ESTABLISHED:
{
return Types::AssociationState::CONNECTED;
}
case State::SHUTDOWN_PENDING:
case State::SHUTDOWN_SENT:
case State::SHUTDOWN_RECEIVED:
case State::SHUTDOWN_ACK_SENT:
{
return Types::AssociationState::SHUTTING_DOWN;
}
NO_DEFAULT_GCC();
}
}
void Association::MayConnect()
{
MS_TRACE();
if (this->state != State::NEW)
{
MS_DEBUG_DEV("internal Association state is not NEW, ignoring");
return;
}
if (this->privateMetrics.rxPacketsCount == 0 && !this->associationListenerDeferrer.OnAssociationIsTransportReadyForSctp())
{
MS_DEBUG_DEV(
"no SCTP data has been received yet and transport is not ready for SCTP traffic, ignoring");
return;
}
MS_DEBUG_DEV("invoking Connect()");
Connect();
}
void Association::Connect()
{
MS_TRACE();
if (this->state != State::NEW)
{
const auto stateStringView = Association::StateToString(this->state);
MS_WARN_TAG(
sctp,
"cannot initiate the Association since internal state is not NEW but %.*s",
static_cast<int>(stateStringView.size()),
stateStringView.data());
return;
}
const AssociationListenerDeferrer::ScopedDeferrer deferrer(this->associationListenerDeferrer);
this->preTcb.localVerificationTag =
Utils::Crypto::GetRandomUInt<uint32_t>(MinVerificationTag, MaxVerificationTag);
this->preTcb.localInitialTsn =
Utils::Crypto::GetRandomUInt<uint32_t>(MinInitialTsn, MaxInitialTsn);
SendInitChunk();
this->t1InitTimer->Start();
SetState(State::COOKIE_WAIT, "Connect() called");
AssertIsConsistent();
this->associationListenerDeferrer.OnAssociationConnecting();
}
void Association::Shutdown()
{
MS_TRACE();
if (this->state == State::NEW || this->state == State::CLOSED)
{
AssertIsConsistent();
return;
}
const AssociationListenerDeferrer::ScopedDeferrer deferrer(this->associationListenerDeferrer);
if (this->tcb)
{
if (
this->state != State::SHUTDOWN_SENT && this->state != State::SHUTDOWN_ACK_SENT &&
this->state != State::SHUTDOWN_RECEIVED && this->state != State::SHUTDOWN_PENDING)
{
this->t1InitTimer->Stop();
this->t1CookieTimer->Stop();
SetState(State::SHUTDOWN_PENDING, "Shutdown() called");
MaySendShutdownOrShutdownAckChunk();
}
}
else
{
InternalClose(Types::ErrorKind::SUCCESS, "");
}
AssertIsConsistent();
}
void Association::Close()
{
MS_TRACE();
if (this->state == State::NEW || this->state == State::CLOSED)
{
AssertIsConsistent();
return;
}
const AssociationListenerDeferrer::ScopedDeferrer deferrer(this->associationListenerDeferrer);
if (this->tcb)
{
auto packet = this->tcb->CreatePacket();
auto* abortAssociationChunk = packet->BuildChunkInPlace<AbortAssociationChunk>();
auto* userInitiatedAbortErrorCause =
abortAssociationChunk->BuildErrorCauseInPlace<UserInitiatedAbortErrorCause>();
userInitiatedAbortErrorCause->SetUpperLayerAbortReason("Close() called");
userInitiatedAbortErrorCause->Consolidate();
abortAssociationChunk->Consolidate();
this->packetSender.SendPacket(packet.get());
}
InternalClose(Types::ErrorKind::SUCCESS, "");
AssertIsConsistent();
}
std::optional<AssociationMetrics> Association::MakeMetrics() const
{
if (!this->tcb)
{
return std::nullopt;
}
const size_t packetPayloadLength =
this->sctpOptions.mtu - Packet::CommonHeaderLength - DataChunk::DataChunkHeaderLength;
AssociationMetrics metrics{
.txPacketsCount = this->privateMetrics.txPacketsCount,
.txMessagesCount = this->privateMetrics.txMessagesCount,
.rxPacketsCount = this->privateMetrics.rxPacketsCount,
.rxMessagesCount = this->privateMetrics.rxMessagesCount,
.rtxPacketsCount = this->tcb->GetRetransmissionQueue().GetRtxPacketsCount(),
.rtxBytesCount = this->tcb->GetRetransmissionQueue().GetRtxBytesCount(),
.cwndBytes = this->tcb->GetCwnd(),
.srttMs = this->tcb->GetCurrentSrttMs(),
.unackDataCount = this->tcb->GetRetransmissionQueue().GetUnackedItems() +
((this->sendQueue.GetTotalBufferedAmount() + packetPayloadLength - 1) /
packetPayloadLength),
.peerRwndBytes = static_cast<uint32_t>(this->tcb->GetRetransmissionQueue().GetRwnd()),
.peerImplementation = this->privateMetrics.peerImplementation,
.negotiatedMaxOutboundStreams = this->privateMetrics.negotiatedMaxOutboundStreams,
.negotiatedMaxInboundStreams = this->privateMetrics.negotiatedMaxInboundStreams,
.usesPartialReliability = this->privateMetrics.usesPartialReliability,
.usesMessageInterleaving = this->privateMetrics.usesMessageInterleaving,
.usesReConfig = this->privateMetrics.usesReConfig,
.usesZeroChecksum = this->privateMetrics.usesZeroChecksum,
};
return metrics;
}
uint16_t Association::GetStreamPriority(uint16_t streamId) const
{
MS_TRACE();
return this->sendQueue.GetStreamPriority(streamId);
}
void Association::SetStreamPriority(uint16_t streamId, uint16_t priority)
{
MS_TRACE();
this->sendQueue.SetStreamPriority(streamId, priority);
}
void Association::SetMaxSendMessageSize(size_t maxMessageSize)
{
MS_TRACE();
this->sctpOptions.maxSendMessageSize = maxMessageSize;
}
size_t Association::GetTotalBufferedAmount() const
{
MS_TRACE();
return this->sendQueue.GetTotalBufferedAmount();
}
size_t Association::GetStreamBufferedAmount(uint16_t streamId) const
{
MS_TRACE();
return this->sendQueue.GetStreamBufferedAmount(streamId);
}
size_t Association::GetStreamBufferedAmountLowThreshold(uint16_t streamId) const
{
MS_TRACE();
return this->sendQueue.GetStreamBufferedAmountLowThreshold(streamId);
}
void Association::SetStreamBufferedAmountLowThreshold(uint16_t streamId, size_t bytes)
{
MS_TRACE();
this->sendQueue.SetStreamBufferedAmountLowThreshold(streamId, bytes);
}
Types::ResetStreamsStatus Association::ResetStreams(std::span<const uint16_t> outboundStreamIds)
{
MS_TRACE();
const AssociationListenerDeferrer::ScopedDeferrer deferrer(this->associationListenerDeferrer);
if (!this->tcb)
{
this->associationListenerDeferrer.OnAssociationError(
Types::ErrorKind::WRONG_SEQUENCE,
"cannot reset outbound streams as the association is not connected");
return Types::ResetStreamsStatus::NOT_CONNECTED;
}
if (!this->tcb->GetNegotiatedCapabilities().reConfig)
{
this->associationListenerDeferrer.OnAssociationError(
Types::ErrorKind::UNSUPPORTED_OPERATION,
"cannot reset outbound streams as the remote doesn't support it");
return Types::ResetStreamsStatus::NOT_SUPPORTED;
}
this->tcb->GetStreamResetHandler().ResetStreams(outboundStreamIds);
MaySendResetStreamsRequest();
AssertIsConsistent();
return Types::ResetStreamsStatus::PERFORMED;
}
Types::SendMessageStatus Association::SendMessage(
Message message, const SendMessageOptions& sendMessageOptions)
{
MS_TRACE();
const AssociationListenerDeferrer::ScopedDeferrer deferrer(this->associationListenerDeferrer);
const auto status = InternalSendMessageCheck(message, sendMessageOptions);
if (status != Types::SendMessageStatus::SUCCESS)
{
return status;
}
const uint64_t nowMs = this->shared->GetTimeMs();
this->privateMetrics.txMessagesCount++;
this->sendQueue.AddMessage(nowMs, std::move(message), sendMessageOptions);
if (this->tcb)
{
this->tcb->SendBufferedPackets(nowMs);
}
AssertIsConsistent();
return Types::SendMessageStatus::SUCCESS;
}
std::vector<Types::SendMessageStatus> Association::SendManyMessages(
std::span<Message> messages, const SendMessageOptions& sendMessageOptions)
{
MS_TRACE();
const AssociationListenerDeferrer::ScopedDeferrer deferrer(this->associationListenerDeferrer);
const uint64_t nowMs = this->shared->GetTimeMs();
std::vector<Types::SendMessageStatus> statuses;
statuses.reserve(messages.size());
for (auto& message : messages)
{
const auto status = InternalSendMessageCheck(message, sendMessageOptions);
statuses.push_back(status);
if (status != Types::SendMessageStatus::SUCCESS)
{
continue;
}
this->privateMetrics.txMessagesCount++;
this->sendQueue.AddMessage(nowMs, std::move(message), sendMessageOptions);
}
if (this->tcb)
{
this->tcb->SendBufferedPackets(nowMs);
}
AssertIsConsistent();
return statuses;
}
void Association::ReceiveSctpData(const uint8_t* data, size_t len)
{
MS_TRACE();
#if MS_LOG_DEV_LEVEL == 3
const auto* packet = RTC::SCTP::Packet::Parse(data, len);
if (packet)
{
MS_DUMP("<<< received SCTP packet:");
packet->Dump();
delete packet;
}
else
{
MS_ERROR("RTC::SCTP::Packet::Parse() failed to parse received SCTP data");
}
#endif
this->privateMetrics.rxPacketsCount++;
MayConnect();
const AssociationListenerDeferrer::ScopedDeferrer deferrer(this->associationListenerDeferrer);
std::unique_ptr<Packet> receivedPacket{ Packet::Parse(data, len) };
if (!receivedPacket)
{
MS_WARN_TAG(sctp, "failed to parse received SCTP packet");
this->associationListenerDeferrer.OnAssociationError(
Types::ErrorKind::PARSE_FAILED, "failed to parse received SCTP packet");
AssertIsConsistent();
return;
}
if (!ValidateReceivedPacket(receivedPacket.get()))
{
MS_WARN_TAG(sctp, "Packet verification failed, discarded");
return;
}
MaySendShutdownOnPacketReceived(receivedPacket.get());
for (auto it = receivedPacket->ChunksBegin(); it != receivedPacket->ChunksEnd(); ++it)
{
const auto* receivedChunk = *it;
if (!HandleReceivedChunk(receivedPacket.get(), receivedChunk))
{
break;
}
}
if (this->tcb)
{
this->tcb->GetDataTracker().ObservePacketEnd();
this->tcb->MaySendSackChunk();
}
AssertIsConsistent();
}
uint16_t Association::GetNegotiatedMaxOutboundStreams() const
{
MS_TRACE();
if (this->tcb)
{
return this->tcb->GetNegotiatedCapabilities().negotiatedMaxOutboundStreams;
}
else
{
MS_WARN_TAG(
sctp,
"calling Association::GetNegotiatedMaxOutboundStreams() before TCB is created returns 0");
return 0;
}
}
uint16_t Association::GetNegotiatedMaxInboundStreams() const
{
MS_TRACE();
if (this->tcb)
{
return this->tcb->GetNegotiatedCapabilities().negotiatedMaxInboundStreams;
}
else
{
MS_WARN_TAG(
sctp,
"calling Association::GetNegotiatedMaxInboundStreams() before TCB is created returns 0");
return 0;
}
}
void Association::InternalClose(Types::ErrorKind errorKind, const std::string_view& message)
{
MS_TRACE();
if (this->state != State::NEW && this->state != State::CLOSED)
{
this->t1InitTimer->Stop();
this->t1CookieTimer->Stop();
this->t2ShutdownTimer->Stop();
this->tcb = nullptr;
}
const auto prevState = this->state;
SetState(State::CLOSED, message);
if (prevState == State::COOKIE_WAIT || prevState == State::COOKIE_ECHOED)
{
if (errorKind == Types::ErrorKind::SUCCESS)
{
this->associationListenerDeferrer.OnAssociationClosed(errorKind, message);
}
else
{
this->associationListenerDeferrer.OnAssociationFailed(errorKind, message);
}
}
else
{
this->associationListenerDeferrer.OnAssociationClosed(errorKind, message);
}
}
void Association::SetState(State state, const std::string_view& message)
{
MS_TRACE();
const auto stateStringView = Association::StateToString(state);
if (state == this->state)
{
MS_WARN_DEV(
"SCTP Association internal state is already %.*s (message:\"%.*s\")",
static_cast<int>(stateStringView.size()),
stateStringView.data(),
static_cast<int>(message.size()),
message.data());
return;
}
const auto previousStateStringView = Association::StateToString(this->state);
MS_DEBUG_TAG(
sctp,
"SCTP Association internal state changed from %.*s to %.*s (message:\"%.*s\")",
static_cast<int>(previousStateStringView.size()),
previousStateStringView.data(),
static_cast<int>(stateStringView.size()),
stateStringView.data(),
static_cast<int>(message.size()),
message.data());
this->state = state;
}
void Association::AddCapabilitiesParametersToInitOrInitAckChunk(AnyInitChunk* chunk) const
{
MS_TRACE();
auto* supportedExtensionsParameter =
chunk->BuildParameterInPlace<SupportedExtensionsParameter>();
supportedExtensionsParameter->AddChunkType(Chunk::ChunkType::RE_CONFIG);
if (this->sctpOptions.enablePartialReliability)
{
supportedExtensionsParameter->AddChunkType(Chunk::ChunkType::FORWARD_TSN);
}
if (this->sctpOptions.enableMessageInterleaving)
{
supportedExtensionsParameter->AddChunkType(Chunk::ChunkType::I_DATA);
supportedExtensionsParameter->AddChunkType(Chunk::ChunkType::I_FORWARD_TSN);
}
supportedExtensionsParameter->Consolidate();
if (this->sctpOptions.enablePartialReliability)
{
const auto* forwardTsnSupportedParameter =
chunk->BuildParameterInPlace<ForwardTsnSupportedParameter>();
forwardTsnSupportedParameter->Consolidate();
}
if (
this->sctpOptions.zeroChecksumAlternateErrorDetectionMethod !=
ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod::NONE)
{
auto* zeroChecksumAcceptableParameter =
chunk->BuildParameterInPlace<ZeroChecksumAcceptableParameter>();
zeroChecksumAcceptableParameter->SetAlternateErrorDetectionMethod(
this->sctpOptions.zeroChecksumAlternateErrorDetectionMethod);
zeroChecksumAcceptableParameter->Consolidate();
}
}
void Association::CreateTransmissionControlBlock(
uint32_t localVerificationTag,
uint32_t remoteVerificationTag,
uint32_t localInitialTsn,
uint32_t remoteInitialTsn,
uint32_t remoteAdvertisedReceiverWindowCredit,
uint64_t tieTag,
const NegotiatedCapabilities& negotiatedCapabilities)
{
MS_TRACE();
this->tcb = std::make_unique<TransmissionControlBlock>(
this->associationListenerDeferrer,
this->sctpOptions,
this->shared,
this->sendQueue,
this->packetSender,
localVerificationTag,
remoteVerificationTag,
localInitialTsn,
remoteInitialTsn,
remoteAdvertisedReceiverWindowCredit,
tieTag,
negotiatedCapabilities,
this->maxPacketLength,
[this]()
{
return this->state == State::ESTABLISHED;
});
this->privateMetrics.negotiatedMaxOutboundStreams =
negotiatedCapabilities.negotiatedMaxOutboundStreams;
this->privateMetrics.negotiatedMaxInboundStreams =
negotiatedCapabilities.negotiatedMaxInboundStreams;
this->privateMetrics.usesPartialReliability = negotiatedCapabilities.partialReliability;
this->privateMetrics.usesMessageInterleaving = negotiatedCapabilities.messageInterleaving;
this->privateMetrics.usesReConfig = negotiatedCapabilities.reConfig;
this->privateMetrics.usesZeroChecksum = negotiatedCapabilities.zeroChecksum;
}
std::unique_ptr<Packet> Association::CreatePacket() const
{
MS_TRACE();
return CreatePacketWithVerificationTag(0);
}
std::unique_ptr<Packet> Association::CreatePacketWithVerificationTag(uint32_t verificationTag) const
{
MS_TRACE();
auto packet =
std::unique_ptr<Packet>{ Packet::Factory(PacketFactoryBuffer, this->maxPacketLength) };
packet->SetSourcePort(this->sctpOptions.sourcePort);
packet->SetDestinationPort(this->sctpOptions.destinationPort);
packet->SetVerificationTag(verificationTag);
return packet;
}
void Association::SendInitChunk()
{
MS_TRACE();
auto packet = CreatePacket();
auto* initChunk = packet->BuildChunkInPlace<InitChunk>();
initChunk->SetInitiateTag(this->preTcb.localVerificationTag);
initChunk->SetAdvertisedReceiverWindowCredit(this->sctpOptions.maxReceiverWindowBufferSize);
initChunk->SetNumberOfOutboundStreams(this->sctpOptions.announcedMaxOutboundStreams);
initChunk->SetNumberOfInboundStreams(this->sctpOptions.announcedMaxInboundStreams);
initChunk->SetInitialTsn(this->preTcb.localInitialTsn);
AddCapabilitiesParametersToInitOrInitAckChunk(initChunk);
initChunk->Consolidate();
this->packetSender.SendPacket(packet.get());
}
void Association::SendShutdownChunk()
{
MS_TRACE();
AssertHasTcb();
auto packet = this->tcb->CreatePacket();
auto* shutdownChunk = packet->BuildChunkInPlace<ShutdownChunk>();
shutdownChunk->SetCumulativeTsnAck(this->tcb->GetDataTracker().GetLastCumulativeAckedTsn());
shutdownChunk->Consolidate();
this->packetSender.SendPacket(packet.get());
}
void Association::SendShutdownAckChunk()
{
MS_TRACE();
AssertHasTcb();
auto packet = this->tcb->CreatePacket();
const auto* shutdownAckChunk = packet->BuildChunkInPlace<ShutdownAckChunk>();
shutdownAckChunk->Consolidate();
this->packetSender.SendPacket(packet.get());
this->t2ShutdownTimer->SetBaseTimeoutMs(this->tcb->GetCurrentRtoMs());
this->t2ShutdownTimer->Start();
}
void Association::MaySendShutdownOrShutdownAckChunk()
{
MS_TRACE();
AssertHasTcb();
if (this->tcb->GetRetransmissionQueue().GetUnackedItems() != 0)
{
return;
}
if (this->state == State::SHUTDOWN_PENDING)
{
SendShutdownChunk();
this->t2ShutdownTimer->SetBaseTimeoutMs(this->tcb->GetCurrentRtoMs());
this->t2ShutdownTimer->Start();
SetState(State::SHUTDOWN_SENT, "no more outstanding data");
}
else if (this->state == State::SHUTDOWN_RECEIVED)
{
SendShutdownAckChunk();
SetState(State::SHUTDOWN_ACK_SENT, "no more outstanding data");
}
}
void Association::MaySendShutdownOnPacketReceived(const Packet* receivedPacket)
{
MS_TRACE();
if (this->state != State::SHUTDOWN_SENT)
{
return;
}
AssertHasTcb();
const bool hasDataChunk = std::find_if(
receivedPacket->ChunksBegin(),
receivedPacket->ChunksEnd(),
[](const Chunk* chunk)
{
return chunk->GetType() == Chunk::ChunkType::DATA ||
chunk->GetType() == Chunk::ChunkType::I_DATA;
}) != receivedPacket->ChunksEnd();
if (hasDataChunk)
{
SendShutdownChunk();
this->t2ShutdownTimer->SetBaseTimeoutMs(this->tcb->GetCurrentRtoMs());
this->t2ShutdownTimer->Start();
}
}
void Association::MaySendResetStreamsRequest()
{
MS_TRACE();
AssertHasTcb();
if (this->tcb->GetStreamResetHandler().ShouldSendStreamResetRequest())
{
auto packet = this->tcb->CreatePacket();
this->tcb->GetStreamResetHandler().AddStreamResetRequest(packet.get());
this->packetSender.SendPacket(packet.get());
}
}
void Association::MayDeliverMessages()
{
MS_TRACE();
AssertHasTcb();
while (std::optional<Message> message = this->tcb->GetReassemblyQueue().GetNextMessage())
{
this->privateMetrics.rxMessagesCount++;
if (message->GetPayloadLength() > this->sctpOptions.maxReceiveMessageSize)
{
MS_WARN_TAG(
sctp,
"dropping too large received message [messageByteLength:%zu, maxReceiveMessageSize:%zu]",
message->GetPayloadLength(),
this->sctpOptions.maxReceiveMessageSize);
break;
}
this->associationListenerDeferrer.OnAssociationMessageReceived(*std::move(message));
}
}
Types::SendMessageStatus Association::InternalSendMessageCheck(
const Message& message, const SendMessageOptions& sendMessageOptions)
{
MS_TRACE();
const auto lifecycleId = sendMessageOptions.lifecycleId;
if (message.GetPayloadLength() == 0)
{
if (lifecycleId.has_value())
{
this->associationListenerDeferrer.OnAssociationLifecycleMessageEnd(lifecycleId.value());
}
this->associationListenerDeferrer.OnAssociationError(
Types::ErrorKind::PROTOCOL_VIOLATION, "cannot send empty message");
return Types::SendMessageStatus::ERROR_MESSAGE_EMPTY;
}
else if (message.GetPayloadLength() > this->sctpOptions.maxSendMessageSize)
{
if (lifecycleId.has_value())
{
this->associationListenerDeferrer.OnAssociationLifecycleMessageEnd(lifecycleId.value());
}
this->associationListenerDeferrer.OnAssociationError(
Types::ErrorKind::PROTOCOL_VIOLATION, "cannot send too large message");
return Types::SendMessageStatus::ERROR_MESSAGE_TOO_LARGE;
}
else if (
this->state == State::SHUTDOWN_PENDING || this->state == State::SHUTDOWN_SENT ||
this->state == State::SHUTDOWN_RECEIVED || this->state == State::SHUTDOWN_ACK_SENT)
{
if (lifecycleId.has_value())
{
this->associationListenerDeferrer.OnAssociationLifecycleMessageEnd(lifecycleId.value());
}
this->associationListenerDeferrer.OnAssociationError(
Types::ErrorKind::WRONG_SEQUENCE,
"cannot send message as the association is shutting down");
return Types::SendMessageStatus::ERROR_SHUTTING_DOWN;
}
else if (
this->sendQueue.GetTotalBufferedAmount() >= this->sctpOptions.maxSendBufferSize ||
this->sendQueue.GetStreamBufferedAmount(message.GetStreamId()) >=
this->sctpOptions.perStreamSendQueueLimit)
{
if (lifecycleId.has_value())
{
this->associationListenerDeferrer.OnAssociationLifecycleMessageEnd(lifecycleId.value());
}
this->associationListenerDeferrer.OnAssociationError(
Types::ErrorKind::RESOURCE_EXHAUSTION, "cannot send message as the send queue is full");
return Types::SendMessageStatus::ERROR_RESOURCE_EXHAUSTION;
}
return Types::SendMessageStatus::SUCCESS;
}
bool Association::ValidateReceivedPacket(const Packet* receivedPacket)
{
MS_TRACE();
const uint32_t localVerificationTag = this->tcb ? this->tcb->GetLocalVerificationTag() : 0;
if (receivedPacket->GetVerificationTag() == 0)
{
if (receivedPacket->GetChunksCount() == 1 && receivedPacket->GetChunkAt(0)->GetType() == Chunk::ChunkType::INIT)
{
return true;
}
else
{
MS_WARN_TAG(
sctp,
"Packet with Verification Tag 0 must have a single Chunk and it must be an INIT Chunk, packet discarded");
this->associationListenerDeferrer.OnAssociationError(
Types::ErrorKind::PARSE_FAILED,
"packet with Verification Tag 0 must have a single chunk and it must be an INIT chunk");
return false;
}
}
if (receivedPacket->GetChunksCount() == 1 && receivedPacket->GetChunkAt(0)->GetType() == Chunk::ChunkType::ABORT)
{
const auto* abortAssociationChunk =
static_cast<const AbortAssociationChunk*>(receivedPacket->GetChunkAt(0));
if (abortAssociationChunk->GetT() && !this->tcb)
{
return true;
}
else if (
(!abortAssociationChunk->GetT() &&
receivedPacket->GetVerificationTag() == localVerificationTag) ||
(abortAssociationChunk->GetT() &&
receivedPacket->GetVerificationTag() == this->tcb->GetRemoteVerificationTag()))
{
return true;
}
else
{
MS_WARN_TAG(
sctp,
"ABORT Chunk Verification Tag %" PRIu32 " is wrong, packet discarded",
receivedPacket->GetVerificationTag());
this->associationListenerDeferrer.OnAssociationError(
Types::ErrorKind::PARSE_FAILED, "packet with ABORT chunk has invalid Verification Tag");
return false;
}
}
if (receivedPacket->GetChunksCount() >= 1 && receivedPacket->GetChunkAt(0)->GetType() == Chunk::ChunkType::INIT_ACK)
{
if (receivedPacket->GetVerificationTag() == this->preTcb.localVerificationTag)
{
return true;
}
else
{
MS_WARN_TAG(
sctp,
"INIT_ACK Chunk Verification Tag %" PRIu32 " (should be %" PRIu32 ")",
receivedPacket->GetVerificationTag(),
this->preTcb.localVerificationTag);
this->associationListenerDeferrer.OnAssociationError(
Types::ErrorKind::PARSE_FAILED,
"packet with INIT_ACK chunk has invalid Verification Tag");
return false;
}
}
if (receivedPacket->GetChunksCount() >= 1 && receivedPacket->GetChunkAt(0)->GetType() == Chunk::ChunkType::COOKIE_ECHO)
{
return true;
}
if (receivedPacket->GetChunksCount() == 1 && receivedPacket->GetChunkAt(0)->GetType() == Chunk::ChunkType::SHUTDOWN_COMPLETE)
{
const auto* shutdownCompleteChunk =
static_cast<const ShutdownCompleteChunk*>(receivedPacket->GetChunkAt(0));
if (shutdownCompleteChunk->GetT() && !this->tcb)
{
return true;
}
else if (
(!shutdownCompleteChunk->GetT() &&
receivedPacket->GetVerificationTag() == localVerificationTag) ||
(shutdownCompleteChunk->GetT() &&
receivedPacket->GetVerificationTag() == this->tcb->GetRemoteVerificationTag()))
{
return true;
}
else
{
MS_WARN_TAG(
sctp,
"SHUTDOWN_COMPLETE Chunk Verification Tag %" PRIu32 " is wrong, packet discarded",
receivedPacket->GetVerificationTag());
this->associationListenerDeferrer.OnAssociationError(
Types::ErrorKind::PARSE_FAILED,
"packet with SHUTDOWN_COMPLETE chunk has invalid Verification Tag");
return false;
}
}
if (receivedPacket->GetVerificationTag() == localVerificationTag)
{
return true;
}
else
{
MS_WARN_TAG(
sctp,
"invalid Verification Tag %" PRIu32 " (should be %" PRIu32 ")",
receivedPacket->GetVerificationTag(),
localVerificationTag);
this->associationListenerDeferrer.OnAssociationError(
Types::ErrorKind::PARSE_FAILED, "packet has invalid Verification Tag");
return false;
}
}
bool Association::HandleReceivedChunk(const Packet* receivedPacket, const Chunk* receivedChunk)
{
MS_TRACE();
switch (receivedChunk->GetType())
{
case Chunk::ChunkType::INIT:
{
HandleReceivedInitChunk(receivedPacket, static_cast<const InitChunk*>(receivedChunk));
break;
}
case Chunk::ChunkType::INIT_ACK:
{
HandleReceivedInitAckChunk(receivedPacket, static_cast<const InitAckChunk*>(receivedChunk));
break;
}
case Chunk::ChunkType::COOKIE_ECHO:
{
HandleReceivedCookieEchoChunk(
receivedPacket, static_cast<const CookieEchoChunk*>(receivedChunk));
break;
}
case Chunk::ChunkType::COOKIE_ACK:
{
HandleReceivedCookieAckChunk(
receivedPacket, static_cast<const CookieAckChunk*>(receivedChunk));
break;
}
case Chunk::ChunkType::SHUTDOWN:
{
HandleReceivedShutdownChunk(
receivedPacket, static_cast<const ShutdownChunk*>(receivedChunk));
break;
}
case Chunk::ChunkType::SHUTDOWN_ACK:
{
HandleReceivedShutdownAckChunk(
receivedPacket, static_cast<const ShutdownAckChunk*>(receivedChunk));
break;
}
case Chunk::ChunkType::SHUTDOWN_COMPLETE:
{
HandleReceivedShutdownCompleteChunk(
receivedPacket, static_cast<const ShutdownCompleteChunk*>(receivedChunk));
break;
}
case Chunk::ChunkType::OPERATION_ERROR:
{
HandleReceivedOperationErrorChunk(
receivedPacket, static_cast<const OperationErrorChunk*>(receivedChunk));
break;
}
case Chunk::ChunkType::ABORT:
{
HandleReceivedAbortAssociationChunk(
receivedPacket, static_cast<const AbortAssociationChunk*>(receivedChunk));
break;
}
case Chunk::ChunkType::HEARTBEAT_REQUEST:
{
HandleReceivedHeartbeatRequestChunk(
receivedPacket, static_cast<const HeartbeatRequestChunk*>(receivedChunk));
break;
}
case Chunk::ChunkType::HEARTBEAT_ACK:
{
HandleReceivedHeartbeatAckChunk(
receivedPacket, static_cast<const HeartbeatAckChunk*>(receivedChunk));
break;
}
case Chunk::ChunkType::RE_CONFIG:
{
HandleReceivedReConfigChunk(
receivedPacket, static_cast<const ReConfigChunk*>(receivedChunk));
break;
}
case Chunk::ChunkType::FORWARD_TSN:
{
HandleReceivedForwardTsnChunk(
receivedPacket, static_cast<const ForwardTsnChunk*>(receivedChunk));
break;
}
case Chunk::ChunkType::I_FORWARD_TSN:
{
HandleReceivedIForwardTsnChunk(
receivedPacket, static_cast<const IForwardTsnChunk*>(receivedChunk));
break;
}
case Chunk::ChunkType::DATA:
{
HandleReceivedDataChunk(receivedPacket, static_cast<const DataChunk*>(receivedChunk));
break;
}
case Chunk::ChunkType::I_DATA:
{
HandleReceivedIDataChunk(receivedPacket, static_cast<const IDataChunk*>(receivedChunk));
break;
}
case Chunk::ChunkType::SACK:
{
HandleReceivedSackChunk(receivedPacket, static_cast<const SackChunk*>(receivedChunk));
break;
}
default:
{
return HandleReceivedUnknownChunk(
receivedPacket, static_cast<const UnknownChunk*>(receivedChunk));
}
}
return true;
}
void Association::HandleReceivedInitChunk(
const Packet* , const InitChunk* receivedInitChunk)
{
MS_TRACE();
if (receivedInitChunk->GetInitiateTag() == 0)
{
MS_WARN_TAG(sctp, "invalid value 0 in Initiate Tagin received INIT Chunk, discarded");
return;
}
else if (
receivedInitChunk->GetNumberOfOutboundStreams() == 0 or
receivedInitChunk->GetNumberOfInboundStreams() == 0)
{
MS_WARN_TAG(
sctp,
"invalidNumber of Outbound Streams or Number of Inbound Streams in received INIT Chunk, aborting Association");
auto packet = CreatePacketWithVerificationTag(0);
auto* abortAssociationChunk = packet->BuildChunkInPlace<AbortAssociationChunk>();
abortAssociationChunk->SetT(true);
auto* protocolViolationErrorCause =
abortAssociationChunk->BuildErrorCauseInPlace<ProtocolViolationErrorCause>();
protocolViolationErrorCause->SetAdditionalInformation(
"invalid value 0 in Number of Outbound Streams or Number of Inbound Streams in received INIT chunk");
protocolViolationErrorCause->Consolidate();
abortAssociationChunk->Consolidate();
this->packetSender.SendPacket(packet.get());
InternalClose(Types::ErrorKind::PROTOCOL_VIOLATION, "received invalid INIT chunk");
return;
}
if (this->state == State::SHUTDOWN_ACK_SENT)
{
MS_DEBUG_TAG(
sctp, "INIT Chunk received in SHUTDOWN_ACK_SENT state, retransmitting SHUTDOWN_ACK Chunk");
SendShutdownAckChunk();
return;
}
uint64_t tieTag{ 0 };
uint32_t localVerificationTag;
uint32_t localInitialTsn;
switch (this->state)
{
case State::NEW:
{
MS_DEBUG_TAG(sctp, "INIT Chunk received in NEW state (normal scenario)");
localVerificationTag =
Utils::Crypto::GetRandomUInt<uint32_t>(MinVerificationTag, MaxVerificationTag);
localInitialTsn = Utils::Crypto::GetRandomUInt<uint32_t>(MinInitialTsn, MaxInitialTsn);
break;
}
case State::CLOSED:
{
MS_WARN_TAG(sctp, "ignoring INIT Chunk received in CLOSED state)");
}
case State::COOKIE_WAIT:
case State::COOKIE_ECHOED:
{
MS_DEBUG_TAG(sctp, "INIT Chunk received after sending INIT Chunk (collision, no problem)");
localVerificationTag = this->preTcb.localVerificationTag;
localInitialTsn = this->preTcb.localInitialTsn;
break;
}
default:
{
AssertHasTcb();
MS_DEBUG_TAG(sctp, "INIT Chunk received (probably peer restarted)");
localVerificationTag =
Utils::Crypto::GetRandomUInt<uint32_t>(MinVerificationTag, MaxVerificationTag);
localInitialTsn = Utils::Crypto::GetRandomUInt<uint32_t>(MinInitialTsn, MaxInitialTsn);
tieTag = this->tcb->GetTieTag();
}
}
MS_DEBUG_TAG(
sctp,
"initiating Association [localVerificationTag:%" PRIu32 ", localInitialTsn:%" PRIu32
", remoteVerificationTag:%" PRIu32 ", remoteInitialTsn:%" PRIu32 "]",
localVerificationTag,
localInitialTsn,
receivedInitChunk->GetInitiateTag(),
receivedInitChunk->GetInitialTsn());
auto packet = CreatePacketWithVerificationTag(receivedInitChunk->GetInitiateTag());
auto* initAckChunk = packet->BuildChunkInPlace<InitAckChunk>();
initAckChunk->SetInitiateTag(localVerificationTag);
initAckChunk->SetAdvertisedReceiverWindowCredit(this->sctpOptions.maxReceiverWindowBufferSize);
initAckChunk->SetNumberOfOutboundStreams(this->sctpOptions.announcedMaxOutboundStreams);
initAckChunk->SetNumberOfInboundStreams(this->sctpOptions.announcedMaxInboundStreams);
initAckChunk->SetInitialTsn(localInitialTsn);
auto* stateCookieParameter = initAckChunk->BuildParameterInPlace<StateCookieParameter>();
const auto negotiatedCapabilities =
NegotiatedCapabilities::Factory(this->sctpOptions, receivedInitChunk);
stateCookieParameter->WriteStateCookieInPlace(
localVerificationTag,
receivedInitChunk->GetInitiateTag(),
localInitialTsn,
receivedInitChunk->GetInitialTsn(),
receivedInitChunk->GetAdvertisedReceiverWindowCredit(),
tieTag,
negotiatedCapabilities);
stateCookieParameter->Consolidate();
AddCapabilitiesParametersToInitOrInitAckChunk(initAckChunk);
initAckChunk->Consolidate();
this->packetSender.SendPacket(
packet.get(), !negotiatedCapabilities.zeroChecksum);
}
void Association::HandleReceivedInitAckChunk(
const Packet* , const InitAckChunk* receivedInitAckChunk)
{
MS_TRACE();
if (this->state != State::COOKIE_WAIT)
{
MS_DEBUG_TAG(sctp, "ignoring received INIT_ACK Chunk when not in COOKIE_WAIT state");
return;
}
const auto* stateCookieParameter =
receivedInitAckChunk->GetFirstParameterOfType<StateCookieParameter>();
if (!stateCookieParameter || !stateCookieParameter->GetCookie())
{
MS_WARN_TAG(
sctp, "ignoring received INIT_ACK Chunk without StateCookieParameter or without Cookie");
auto packet = CreatePacketWithVerificationTag(this->preTcb.localVerificationTag);
auto* abortAssociationChunk = packet->BuildChunkInPlace<AbortAssociationChunk>();
abortAssociationChunk->SetT(true);
auto* protocolViolationErrorCause =
abortAssociationChunk->BuildErrorCauseInPlace<ProtocolViolationErrorCause>();
protocolViolationErrorCause->SetAdditionalInformation(
"INIT_ACK without State Cookie Parameter or without Cookie");
protocolViolationErrorCause->Consolidate();
abortAssociationChunk->Consolidate();
this->packetSender.SendPacket(packet.get());
InternalClose(
Types::ErrorKind::PROTOCOL_VIOLATION, "received INIT_ACK chunk doesn't contain a Cookie");
return;
}
this->privateMetrics.peerImplementation = StateCookie::DetermineSctpImplementation(
stateCookieParameter->GetCookie(), stateCookieParameter->GetCookieLength());
this->t1InitTimer->Stop();
const auto negotiatedCapabilities =
NegotiatedCapabilities::Factory(this->sctpOptions, receivedInitAckChunk);
this->sendQueue.Reset();
CreateTransmissionControlBlock(
this->preTcb.localVerificationTag,
receivedInitAckChunk->GetInitiateTag(),
this->preTcb.localInitialTsn,
receivedInitAckChunk->GetInitialTsn(),
receivedInitAckChunk->GetAdvertisedReceiverWindowCredit(),
Utils::Crypto::GetRandomUInt<uint64_t>(0, MaxTieTag),
negotiatedCapabilities);
SetState(State::COOKIE_ECHOED, "INIT_ACK received");
std::vector<uint8_t> remoteStateCookie(
stateCookieParameter->GetCookie(),
stateCookieParameter->GetCookie() + stateCookieParameter->GetCookieLength());
this->tcb->SetRemoteStateCookie(std::move(remoteStateCookie));
const uint64_t nowMs = this->shared->GetTimeMs();
this->tcb->SendBufferedPackets(nowMs);
this->t1CookieTimer->Start();
this->associationListenerDeferrer.OnAssociationConnecting();
}
void Association::HandleReceivedCookieEchoChunk(
const Packet* receivedPacket, const CookieEchoChunk* receivedCookieEchoChunk)
{
MS_TRACE();
if (!receivedCookieEchoChunk->HasCookie())
{
MS_WARN_TAG(sctp, "ignoring received COOKIE_ECHO Chunk without Cookie");
this->associationListenerDeferrer.OnAssociationError(
Types::ErrorKind::PARSE_FAILED, "received COOKIE_ECHO Chunk without Cookie");
return;
}
std::unique_ptr<StateCookie> cookie{ StateCookie::Parse(
receivedCookieEchoChunk->GetCookie(), receivedCookieEchoChunk->GetCookieLength()) };
if (!cookie)
{
MS_WARN_TAG(sctp, "failed to parse Cookie in received COOKIE_ECHO Chunk");
this->associationListenerDeferrer.OnAssociationError(
Types::ErrorKind::PARSE_FAILED, "received COOKIE_ECHO Chunk with invalid Cookie");
return;
}
if (this->tcb)
{
if (!HandleReceivedCookieEchoChunkWithTcb(receivedPacket, cookie.get()))
{
return;
}
}
else
{
if (receivedPacket->GetVerificationTag() != cookie->GetLocalVerificationTag())
{
MS_WARN_TAG(sctp, "received COOKIE_ECHO Chunk with invalid Verification Tag");
this->associationListenerDeferrer.OnAssociationError(
Types::ErrorKind::PARSE_FAILED,
"received COOKIE_ECHO Chunk with invalid Verification Tag");
return;
}
}
this->t1InitTimer->Stop();
this->t1CookieTimer->Stop();
if (this->state != State::ESTABLISHED)
{
if (this->tcb)
{
this->tcb->ClearRemoteStateCookie();
}
SetState(State::ESTABLISHED, "COOKIE_ECHO received");
this->associationListenerDeferrer.OnAssociationConnected();
}
if (!this->tcb)
{
this->sendQueue.Reset();
CreateTransmissionControlBlock(
cookie->GetLocalVerificationTag(),
cookie->GetRemoteVerificationTag(),
cookie->GetLocalInitialTsn(),
cookie->GetRemoteInitialTsn(),
cookie->GetRemoteAdvertisedReceiverWindowCredit(),
Utils::Crypto::GetRandomUInt<uint64_t>(0, MaxTieTag),
cookie->GetNegotiatedCapabilities());
}
const uint64_t nowMs = this->shared->GetTimeMs();
this->tcb->SendBufferedPackets(nowMs, true);
}
bool Association::HandleReceivedCookieEchoChunkWithTcb(
const Packet* receivedPacket, const StateCookie* cookie)
{
MS_TRACE();
MS_DEBUG_DEV("handling COOKIE_ECHO with TCB");
AssertHasTcb();
if (
receivedPacket->GetVerificationTag() != this->tcb->GetLocalVerificationTag() &&
cookie->GetRemoteVerificationTag() != this->tcb->GetRemoteVerificationTag() &&
cookie->GetTieTag() == this->tcb->GetTieTag())
{
if (this->state == State::SHUTDOWN_ACK_SENT)
{
auto packet = CreatePacketWithVerificationTag(cookie->GetRemoteVerificationTag());
const auto* shutdownAckChunk = packet->BuildChunkInPlace<ShutdownAckChunk>();
shutdownAckChunk->Consolidate();
auto* operationErrorChunk = packet->BuildChunkInPlace<OperationErrorChunk>();
const auto* cookieReceivedWhileShuttingDownErrorCause =
operationErrorChunk->BuildErrorCauseInPlace<CookieReceivedWhileShuttingDownErrorCause>();
cookieReceivedWhileShuttingDownErrorCause->Consolidate();
operationErrorChunk->Consolidate();
this->packetSender.SendPacket(packet.get());
this->associationListenerDeferrer.OnAssociationError(
Types::ErrorKind::WRONG_SEQUENCE, "received COOKIE_ECHO while shutting down");
return false;
}
MS_DEBUG_DEV("received COOKIE_ECHO indicating a restarted peer");
this->tcb = nullptr;
this->associationListenerDeferrer.OnAssociationRestarted();
}
else if (
receivedPacket->GetVerificationTag() == this->tcb->GetLocalVerificationTag() &&
cookie->GetRemoteVerificationTag() != this->tcb->GetRemoteVerificationTag())
{
MS_DEBUG_DEV("received COOKIE_ECHO indicating simultaneous associations");
this->tcb = nullptr;
}
else if (
receivedPacket->GetVerificationTag() != this->tcb->GetLocalVerificationTag() &&
cookie->GetRemoteVerificationTag() == this->tcb->GetRemoteVerificationTag() &&
cookie->GetTieTag() == this->tcb->GetTieTag())
{
MS_DEBUG_DEV("received COOKIE_ECHO indicating a late COOKIE_ECHO, discarding");
return false;
}
else if (
receivedPacket->GetVerificationTag() == this->tcb->GetLocalVerificationTag() &&
cookie->GetRemoteVerificationTag() == this->tcb->GetRemoteVerificationTag())
{
MS_DEBUG_DEV(
"received duplicate COOKIE_ECHO, probably because of peer not receiving COOKIE_ACK and retransmitting COOKIE_ECHO");
}
return true;
}
void Association::HandleReceivedCookieAckChunk(
const Packet* , const CookieAckChunk* )
{
MS_TRACE();
if (this->state != State::COOKIE_ECHOED)
{
MS_DEBUG_DEV("received COOKIE_ACK not in COOKIE_ECHOED state, discarding");
return;
}
AssertHasTcb();
this->t1CookieTimer->Stop();
this->tcb->ClearRemoteStateCookie();
SetState(State::ESTABLISHED, "COOKIE_ACK received");
const uint64_t nowMs = this->shared->GetTimeMs();
this->tcb->SendBufferedPackets(nowMs);
this->associationListenerDeferrer.OnAssociationConnected();
}
void Association::HandleReceivedShutdownChunk(
const Packet* , const ShutdownChunk* )
{
MS_TRACE();
switch (this->state)
{
case State::NEW:
case State::CLOSED:
{
break;
}
case State::COOKIE_WAIT:
case State::COOKIE_ECHOED:
{
break;
}
case State::SHUTDOWN_SENT:
{
SendShutdownAckChunk();
SetState(State::SHUTDOWN_ACK_SENT, "SHUTDOWN received");
break;
}
case State::SHUTDOWN_ACK_SENT:
{
SendShutdownAckChunk();
break;
}
case State::SHUTDOWN_RECEIVED:
{
break;
}
default:
{
MS_DEBUG_DEV("received SHUTDOWN, shutting down the Association");
SetState(State::SHUTDOWN_RECEIVED, "SHUTDOWN received");
MaySendShutdownOrShutdownAckChunk();
}
}
}
void Association::HandleReceivedShutdownAckChunk(
const Packet* receivedPacket, const ShutdownAckChunk* )
{
MS_TRACE();
switch (this->state)
{
case State::SHUTDOWN_SENT:
case State::SHUTDOWN_ACK_SENT:
{
auto packet = this->tcb->CreatePacket();
const auto* shutdownCompleteChunk = packet->BuildChunkInPlace<ShutdownCompleteChunk>();
shutdownCompleteChunk->Consolidate();
this->packetSender.SendPacket(packet.get());
InternalClose(Types::ErrorKind::SUCCESS, "");
break;
}
default:
{
auto packet = this->CreatePacketWithVerificationTag(receivedPacket->GetVerificationTag());
auto* shutdownCompleteChunk = packet->BuildChunkInPlace<ShutdownCompleteChunk>();
shutdownCompleteChunk->SetT(true);
shutdownCompleteChunk->Consolidate();
this->packetSender.SendPacket(packet.get());
}
}
}
void Association::HandleReceivedShutdownCompleteChunk(
const Packet* , const ShutdownCompleteChunk* )
{
MS_TRACE();
if (this->state != State::SHUTDOWN_ACK_SENT)
{
return;
}
InternalClose(Types::ErrorKind::SUCCESS, "");
}
void Association::HandleReceivedOperationErrorChunk(
const Packet* , const OperationErrorChunk* receivedOperationErrorChunk)
{
MS_TRACE();
std::string errorCausesStr;
errorCausesStr.reserve(50);
for (auto it = receivedOperationErrorChunk->ErrorCausesBegin();
it != receivedOperationErrorChunk->ErrorCausesEnd();
++it)
{
const auto* errorCause = *it;
if (!errorCausesStr.empty())
{
errorCausesStr.append(", ");
}
errorCausesStr.append(errorCause->ToString());
}
if (!this->tcb)
{
MS_DEBUG_TAG(
sctp,
"received OPERATION_ERROR Chunk on a Association with no TCB, ignoring: %s",
errorCausesStr.c_str());
return;
}
MS_WARN_TAG(sctp, "received OPERATION_ERROR Chunk: %s", errorCausesStr.c_str());
this->associationListenerDeferrer.OnAssociationError(
Types::ErrorKind::PEER_REPORTED, errorCausesStr);
}
void Association::HandleReceivedAbortAssociationChunk(
const Packet* , const AbortAssociationChunk* receivedAbortAssociationChunk)
{
MS_TRACE();
std::string errorCausesStr;
errorCausesStr.reserve(50);
for (auto it = receivedAbortAssociationChunk->ErrorCausesBegin();
it != receivedAbortAssociationChunk->ErrorCausesEnd();
++it)
{
const auto* errorCause = *it;
if (!errorCausesStr.empty())
{
errorCausesStr.append(", ");
}
errorCausesStr.append(errorCause->ToString());
}
if (!this->tcb)
{
MS_DEBUG_TAG(
sctp,
"received ABORT Chunk on a Association with no TCB, ignoring: %s",
errorCausesStr.c_str());
return;
}
MS_WARN_TAG(sctp, "received ABORT Chunk, closing Association: %s", errorCausesStr.c_str());
InternalClose(Types::ErrorKind::PEER_REPORTED, errorCausesStr);
}
void Association::HandleReceivedHeartbeatRequestChunk(
const Packet* , const HeartbeatRequestChunk* receivedHeartbeatRequestChunk)
{
MS_TRACE();
if (!ValidateHasTcb())
{
return;
}
this->tcb->GetHeartbeatHandler().HandleReceivedHeartbeatRequestChunk(
receivedHeartbeatRequestChunk);
}
void Association::HandleReceivedHeartbeatAckChunk(
const Packet* , const HeartbeatAckChunk* receivedHeartbeatAckChunk)
{
MS_TRACE();
if (!ValidateHasTcb())
{
return;
}
this->tcb->GetHeartbeatHandler().HandleReceivedHeartbeatAckChunk(receivedHeartbeatAckChunk);
}
void Association::HandleReceivedReConfigChunk(
const Packet* , const ReConfigChunk* receivedReConfigChunk)
{
MS_TRACE();
if (!ValidateHasTcb())
{
return;
}
this->tcb->GetStreamResetHandler().HandleReceivedReConfigChunk(receivedReConfigChunk);
MaySendResetStreamsRequest();
const uint64_t nowMs = this->shared->GetTimeMs();
this->tcb->SendBufferedPackets(nowMs);
MayDeliverMessages();
}
void Association::HandleReceivedForwardTsnChunk(
const Packet* receivedPacket, const ForwardTsnChunk* receivedForwardTsnChunk)
{
MS_TRACE();
HandleReceivedAnyForwardTsnChunk(receivedPacket, receivedForwardTsnChunk);
}
void Association::HandleReceivedIForwardTsnChunk(
const Packet* receivedPacket, const IForwardTsnChunk* receivedIForwardTsnChunk)
{
MS_TRACE();
HandleReceivedAnyForwardTsnChunk(receivedPacket, receivedIForwardTsnChunk);
}
void Association::HandleReceivedAnyForwardTsnChunk(
const Packet* , const AnyForwardTsnChunk* receivedAnyForwardTsnChunk)
{
MS_TRACE();
if (!ValidateHasTcb())
{
return;
}
if (!this->tcb->GetNegotiatedCapabilities().partialReliability)
{
auto packet = this->tcb->CreatePacket();
auto* abortAssociationChunk = packet->BuildChunkInPlace<AbortAssociationChunk>();
auto* protocolViolationErrorCause =
abortAssociationChunk->BuildErrorCauseInPlace<ProtocolViolationErrorCause>();
protocolViolationErrorCause->SetAdditionalInformation(
"FORWARD-TSN or I_FORWARD-TSN chunk received but partial reliability is not negotiated");
protocolViolationErrorCause->Consolidate();
abortAssociationChunk->Consolidate();
this->packetSender.SendPacket(packet.get());
this->associationListenerDeferrer.OnAssociationError(
Types::ErrorKind::PROTOCOL_VIOLATION,
"received FORWARD-TSN or I-FORWARD-TSN chunk but partial reliability is not negotiated");
return;
}
if (this->tcb->GetDataTracker().HandleForwardTsn(receivedAnyForwardTsnChunk->GetNewCumulativeTsn()))
{
this->tcb->GetReassemblyQueue().HandleForwardTsn(
receivedAnyForwardTsnChunk->GetNewCumulativeTsn(),
receivedAnyForwardTsnChunk->GetSkippedStreams());
}
MayDeliverMessages();
}
void Association::HandleReceivedDataChunk(
const Packet* receivedPacket, const DataChunk* receivedDataChunk)
{
MS_TRACE();
HandleReceivedAnyDataChunk(receivedPacket, receivedDataChunk);
}
void Association::HandleReceivedIDataChunk(
const Packet* receivedPacket, const IDataChunk* receivedIDataChunk)
{
MS_TRACE();
HandleReceivedAnyDataChunk(receivedPacket, receivedIDataChunk);
}
void Association::HandleReceivedAnyDataChunk(
const Packet* , const AnyDataChunk* receivedAnyDataChunk)
{
MS_TRACE();
if (!ValidateHasTcb())
{
return;
}
const uint32_t tsn = receivedAnyDataChunk->GetTsn();
const bool immediateAck = receivedAnyDataChunk->GetI();
if (receivedAnyDataChunk->GetUserDataPayloadLength() == 0)
{
auto packet = this->tcb->CreatePacket();
auto* operationErrorChunk = packet->BuildChunkInPlace<OperationErrorChunk>();
auto* noUserDataErrorCause =
operationErrorChunk->BuildErrorCauseInPlace<NoUserDataErrorCause>();
noUserDataErrorCause->SetTsn(tsn);
noUserDataErrorCause->Consolidate();
operationErrorChunk->Consolidate();
this->packetSender.SendPacket(packet.get());
this->associationListenerDeferrer.OnAssociationError(
Types::ErrorKind::PROTOCOL_VIOLATION, "received DATA or I-DATA chunk with no user data");
return;
}
MS_DEBUG_DEV(
"data received [data length:%" PRIu16 ", queue size:%zu, watermark:%zu, full:%s, above:%s]",
receivedAnyDataChunk->GetUserDataPayloadLength(),
this->tcb->GetReassemblyQueue().GetQueuedBytes(),
this->tcb->GetReassemblyQueue().GetWatermarkBytes(),
this->tcb->GetReassemblyQueue().IsFull() ? "yes" : "no",
this->tcb->GetReassemblyQueue().IsAboveWatermark() ? "yes" : "no");
if (this->tcb->GetReassemblyQueue().IsFull())
{
if (this->tcb->GetReassemblyQueue().HasMessages())
{
MS_WARN_TAG(sctp, "received data rejected because reassembly queue is full");
return;
}
else
{
auto packet = this->tcb->CreatePacket();
auto* abortAssociationChunk = packet->BuildChunkInPlace<AbortAssociationChunk>();
auto* outOfResourceErrorCause =
abortAssociationChunk->BuildErrorCauseInPlace<OutOfResourceErrorCause>();
outOfResourceErrorCause->Consolidate();
abortAssociationChunk->Consolidate();
this->packetSender.SendPacket(packet.get());
InternalClose(Types::ErrorKind::RESOURCE_EXHAUSTION, "reassembly queue is exhausted");
return;
}
}
if (this->tcb->GetReassemblyQueue().IsAboveWatermark())
{
MS_WARN_TAG(sctp, "reassembly queue is above watermark");
if (!this->tcb->GetDataTracker().WillIncreaseCumAckTsn(tsn))
{
MS_WARN_TAG(sctp, "reassembly queue is above watermark");
this->tcb->GetDataTracker().ForceImmediateSack();
return;
}
}
if (!this->tcb->GetDataTracker().IsTsnValid(tsn))
{
MS_WARN_TAG(sctp, "data rejected because of failing TSN validity");
return;
}
if (this->tcb->GetDataTracker().Observe(tsn, immediateAck))
{
this->tcb->GetReassemblyQueue().AddData(tsn, receivedAnyDataChunk->MakeUserData());
MayDeliverMessages();
}
}
void Association::HandleReceivedSackChunk(
const Packet* , const SackChunk* receivedSackChunk)
{
MS_TRACE();
if (!ValidateHasTcb())
{
return;
}
const uint64_t nowMs = this->shared->GetTimeMs();
if (this->tcb->GetRetransmissionQueue().HandleReceivedSackChunk(nowMs, receivedSackChunk))
{
MaySendShutdownOrShutdownAckChunk();
this->tcb->MaySendFastRetransmit();
this->tcb->SendBufferedPackets(nowMs);
}
else
{
MS_WARN_TAG(
sctp,
"dropping received out-of-order SACK [TSN:%" PRIu32 "]",
receivedSackChunk->GetCumulativeTsnAck());
}
}
bool Association::HandleReceivedUnknownChunk(
const Packet* , const UnknownChunk* receivedUnknownChunk)
{
MS_TRACE();
const auto action = receivedUnknownChunk->GetActionForUnknownChunkType();
const auto skipProcessing = action == Chunk::ActionForUnknownChunkType::SKIP ||
action == Chunk::ActionForUnknownChunkType::SKIP_AND_REPORT;
const auto reportError = action == Chunk::ActionForUnknownChunkType::STOP_AND_REPORT ||
action == Chunk::ActionForUnknownChunkType::SKIP_AND_REPORT;
if (skipProcessing)
{
MS_WARN_TAG(
sctp,
"Chunk with unknown type %" PRIu8
" received, skipping further processing of Chunks in the Packet",
static_cast<uint8_t>(receivedUnknownChunk->GetType()));
}
else
{
MS_DEBUG_TAG(
sctp,
"ignoring received Chunk with unknown type %" PRIu8,
static_cast<uint8_t>(receivedUnknownChunk->GetType()));
}
if (reportError)
{
this->associationListenerDeferrer.OnAssociationError(
Types::ErrorKind::PARSE_FAILED, "unknown chunk with type indicating it should be reported");
if (this->tcb)
{
auto packet = this->tcb->CreatePacket();
auto* operationErrorChunk = packet->BuildChunkInPlace<OperationErrorChunk>();
auto* unrecognizedChunkTypeErrorCause =
operationErrorChunk->BuildErrorCauseInPlace<UnrecognizedChunkTypeErrorCause>();
unrecognizedChunkTypeErrorCause->SetUnrecognizedChunk(
receivedUnknownChunk->GetBuffer(), receivedUnknownChunk->GetLength());
unrecognizedChunkTypeErrorCause->Consolidate();
operationErrorChunk->Consolidate();
this->packetSender.SendPacket(packet.get());
}
}
return !skipProcessing;
}
void Association::OnT1InitTimer(uint64_t& , bool& )
{
MS_TRACE();
const AssociationListenerDeferrer::ScopedDeferrer deferrer(this->associationListenerDeferrer);
AssertState(State::COOKIE_WAIT);
if (this->t1InitTimer->IsRunning())
{
SendInitChunk();
}
else
{
InternalClose(Types::ErrorKind::TOO_MANY_RETRIES, "no INIT_ACK chunk received");
}
AssertIsConsistent();
}
void Association::OnT1CookieTimer(uint64_t& , bool& )
{
MS_TRACE();
const AssociationListenerDeferrer::ScopedDeferrer deferrer(this->associationListenerDeferrer);
AssertState(State::COOKIE_ECHOED);
if (this->t1CookieTimer->IsRunning())
{
const uint64_t nowMs = this->shared->GetTimeMs();
this->tcb->SendBufferedPackets(nowMs);
}
else
{
InternalClose(Types::ErrorKind::TOO_MANY_RETRIES, "no COOKIE_ACK chunk received");
}
AssertIsConsistent();
}
void Association::OnT2ShutdownTimer(uint64_t& baseTimeoutMs, bool& )
{
MS_TRACE();
AssertState(State::SHUTDOWN_SENT, State::SHUTDOWN_ACK_SENT);
AssertHasTcb();
const AssociationListenerDeferrer::ScopedDeferrer deferrer(this->associationListenerDeferrer);
if (!this->t2ShutdownTimer->IsRunning())
{
auto packet = this->tcb->CreatePacket();
auto* abortAssociationChunk = packet->BuildChunkInPlace<AbortAssociationChunk>();
auto* userInitiatedAbortErrorCause =
abortAssociationChunk->BuildErrorCauseInPlace<UserInitiatedAbortErrorCause>();
userInitiatedAbortErrorCause->SetUpperLayerAbortReason(
"too many retransmissions of SHUTDOWN chunk");
userInitiatedAbortErrorCause->Consolidate();
abortAssociationChunk->Consolidate();
this->packetSender.SendPacket(packet.get());
InternalClose(Types::ErrorKind::TOO_MANY_RETRIES, "no SHUTDOWN_ACK chunk received");
AssertIsConsistent();
return;
}
if (this->state == State::SHUTDOWN_ACK_SENT)
{
SendShutdownAckChunk();
}
else
{
SendShutdownChunk();
}
AssertIsConsistent();
baseTimeoutMs = this->tcb->GetCurrentRtoMs();
}
template<typename... States>
void Association::AssertState(States... expectedStates) const
{
MS_TRACE();
static_assert((std::is_same_v<States, State> && ...), "all arguments must be of type State");
if ((... || (this->state == expectedStates)))
{
return;
}
const auto currentStateStringView = Association::StateToString(this->state);
std::ostringstream expectedStatesOss;
bool firstExpectedState = true;
((expectedStatesOss << (firstExpectedState ? "" : ", ")
<< Association::StateToString(expectedStates),
firstExpectedState = false),
...);
auto expectedStatesString = expectedStatesOss.str();
MS_ABORT(
"current internal state %.*s does not match any of the given expected states (%s)",
static_cast<int>(currentStateStringView.size()),
currentStateStringView.data(),
expectedStatesString.c_str());
}
template<typename... States>
void Association::AssertNotState(States... unexpectedStates) const
{
MS_TRACE();
static_assert((std::is_same_v<States, State> && ...), "all arguments must be of type State");
if ((... || (this->state == unexpectedStates)))
{
const auto currentStateStringView = Association::StateToString(this->state);
std::ostringstream unexpectedStatesOss;
bool firstUnexpectedState = true;
((unexpectedStatesOss << (firstUnexpectedState ? "" : ", ")
<< Association::StateToString(unexpectedStates),
firstUnexpectedState = false),
...);
const auto unexpectedStatesString = unexpectedStatesOss.str();
MS_ABORT(
"current internal state %.*s matches one of the given unexpected states (%s)",
static_cast<int>(currentStateStringView.size()),
currentStateStringView.data(),
unexpectedStatesString.c_str());
}
}
bool Association::ValidateHasTcb()
{
MS_TRACE();
if (this->tcb)
{
return true;
}
this->associationListenerDeferrer.OnAssociationError(
Types::ErrorKind::NOT_CONNECTED,
"received unexpected commands on association that is not connected");
return false;
}
void Association::AssertHasTcb() const
{
MS_TRACE();
if (!this->tcb)
{
MS_ABORT("TCB doesn't exist");
}
}
void Association::AssertIsConsistent() const
{
MS_TRACE();
switch (this->state)
{
case State::NEW:
{
MS_ASSERT(!this->tcb, "internal state is NEW but there is TCB");
MS_ASSERT(
!this->t1InitTimer->IsRunning(), "internal state is NEW but T1 Init timer is running");
MS_ASSERT(
!this->t1CookieTimer->IsRunning(),
"internal state is NEW but T1 Cookie timer is running");
MS_ASSERT(
!this->t2ShutdownTimer->IsRunning(),
"internal state is NEW but T2 Shutdown timer is running");
break;
}
case State::CLOSED:
{
MS_ASSERT(!this->tcb, "internal state is CLOSED but there is TCB");
MS_ASSERT(
!this->t1InitTimer->IsRunning(), "internal state is CLOSED but T1 Init timer is running");
MS_ASSERT(
!this->t1CookieTimer->IsRunning(),
"internal state is CLOSED but T1 Cookie timer is running");
MS_ASSERT(
!this->t2ShutdownTimer->IsRunning(),
"internal state is CLOSED but T2 Shutdown timer is running");
break;
}
case State::COOKIE_WAIT:
{
MS_ASSERT(!this->tcb, "internal state is COOKIE_WAIT but there is TCB");
MS_ASSERT(
this->t1InitTimer->IsRunning(),
"internal state is COOKIE_WAIT but T1 Init timer is not running");
MS_ASSERT(
!this->t1CookieTimer->IsRunning(),
"internal state is COOKIE_WAIT but T1 Cookie timer is running");
MS_ASSERT(
!this->t2ShutdownTimer->IsRunning(),
"internal state is COOKIE_WAIT but T2 Shutdown timer is running");
break;
}
case State::COOKIE_ECHOED:
{
MS_ASSERT(this->tcb, "internal state is COOKIE_ECHOED but there is no TCB");
MS_ASSERT(
!this->t1InitTimer->IsRunning(),
"internal state is COOKIE_ECHOED but T1 Init timer is not running");
MS_ASSERT(
this->t1CookieTimer->IsRunning(),
"internal state is COOKIE_ECHOED but T1 Cookie timer is not running");
MS_ASSERT(
!this->t2ShutdownTimer->IsRunning(),
"internal state is COOKIE_ECHOED but T2 Shutdown timer is running");
MS_ASSERT(
this->tcb->HasRemoteStateCookie(),
"internal state is COOKIE_ECHOED but TCB does't have remote state cookie");
break;
}
case State::ESTABLISHED:
{
MS_ASSERT(this->tcb, "internal state is ESTABLISHED but there is not TCB");
MS_ASSERT(
!this->t1InitTimer->IsRunning(),
"internal state is ESTABLISHED but T1 Init timer is running");
MS_ASSERT(
!this->t1CookieTimer->IsRunning(),
"internal state is ESTABLISHED but T1 Cookie timer is running");
MS_ASSERT(
!this->t2ShutdownTimer->IsRunning(),
"internal state is ESTABLISHED but T2 Shutdown timer is running");
break;
}
case State::SHUTDOWN_PENDING:
{
MS_ASSERT(this->tcb, "internal state is SHUTDOWN_PENDING but there is not TCB");
MS_ASSERT(
!this->t1InitTimer->IsRunning(),
"internal state is SHUTDOWN_PENDING but T1 Init timer is running");
MS_ASSERT(
!this->t1CookieTimer->IsRunning(),
"internal state is SHUTDOWN_PENDING but T1 Cookie timer is running");
MS_ASSERT(
!this->t2ShutdownTimer->IsRunning(),
"internal state is SHUTDOWN_PENDING but T2 Shutdown timer is running");
break;
}
case State::SHUTDOWN_SENT:
{
MS_ASSERT(this->tcb, "internal state is SHUTDOWN_SENT but there is not TCB");
MS_ASSERT(
!this->t1InitTimer->IsRunning(),
"internal state is SHUTDOWN_SENT but T1 Init timer is running");
MS_ASSERT(
!this->t1CookieTimer->IsRunning(),
"internal state is SHUTDOWN_SENT but T1 Cookie timer is running");
MS_ASSERT(
this->t2ShutdownTimer->IsRunning(),
"internal state is SHUTDOWN_SENT but T2 Shutdown timer is not running");
break;
}
case State::SHUTDOWN_RECEIVED:
{
MS_ASSERT(this->tcb, "internal state is SHUTDOWN_RECEIVED but there is not TCB");
MS_ASSERT(
!this->t1InitTimer->IsRunning(),
"internal state is SHUTDOWN_RECEIVED but T1 Init timer is running");
MS_ASSERT(
!this->t1CookieTimer->IsRunning(),
"internal state is SHUTDOWN_RECEIVED but T1 Cookie timer is running");
MS_ASSERT(
!this->t2ShutdownTimer->IsRunning(),
"internal state is SHUTDOWN_RECEIVED but T2 Shutdown timer is running");
break;
}
case State::SHUTDOWN_ACK_SENT:
{
MS_ASSERT(this->tcb, "internal state is SHUTDOWN_ACK_SENT but there is not TCB");
MS_ASSERT(
!this->t1InitTimer->IsRunning(),
"internal state is SHUTDOWN_ACK_SENT but T1 Init timer is running");
MS_ASSERT(
!this->t1CookieTimer->IsRunning(),
"internal state is SHUTDOWN_ACK_SENT but T1 Cookie timer is running");
MS_ASSERT(
this->t2ShutdownTimer->IsRunning(),
"internal state is SHUTDOWN_ACK_SENT but T2 Shutdown timer is not running");
break;
}
}
}
#if MS_LOG_DEV_LEVEL == 3
void Association::OnPacketSenderPacketSent(
PacketSender* , const Packet* packet, bool sent)
#else
void Association::OnPacketSenderPacketSent(
PacketSender* , const Packet* , bool sent)
#endif
{
MS_TRACE();
#if MS_LOG_DEV_LEVEL == 3
MS_DUMP(">>> SCTP packet sent [sent:%s]", sent ? "yes" : "no");
packet->Dump();
#endif
if (sent)
{
this->privateMetrics.txPacketsCount++;
}
}
void Association::OnBackoffTimer(
BackoffTimerHandleInterface* backoffTimer, uint64_t& baseTimeoutMs, bool& stop)
{
MS_TRACE();
const auto maxRestarts = backoffTimer->GetMaxRestarts();
MS_DEBUG_TAG(
sctp,
"%s timer has expired [expìrations:%zu/%s]",
backoffTimer->GetLabel().c_str(),
backoffTimer->GetExpirationCount(),
maxRestarts ? std::to_string(maxRestarts.value()).c_str() : "Infinite");
if (backoffTimer == this->t1InitTimer.get())
{
OnT1InitTimer(baseTimeoutMs, stop);
}
else if (backoffTimer == this->t1CookieTimer.get())
{
OnT1CookieTimer(baseTimeoutMs, stop);
}
else if (backoffTimer == this->t2ShutdownTimer.get())
{
OnT2ShutdownTimer(baseTimeoutMs, stop);
}
}
} }