#define MS_CLASS "RTC::SCTP::Packet"
#include "RTC/SCTP/packet/Packet.hpp"
#include "Logger.hpp"
#include "MediaSoupErrors.hpp"
#include "RTC/SCTP/packet/chunks/AbortAssociationChunk.hpp"
#include "RTC/SCTP/packet/chunks/CookieAckChunk.hpp"
#include "RTC/SCTP/packet/chunks/CookieEchoChunk.hpp"
#include "RTC/SCTP/packet/chunks/DataChunk.hpp"
#include "RTC/SCTP/packet/chunks/ForwardTsnChunk.hpp"
#include "RTC/SCTP/packet/chunks/HeartbeatAckChunk.hpp"
#include "RTC/SCTP/packet/chunks/HeartbeatRequestChunk.hpp"
#include "RTC/SCTP/packet/chunks/IDataChunk.hpp"
#include "RTC/SCTP/packet/chunks/IForwardTsnChunk.hpp"
#include "RTC/SCTP/packet/chunks/InitAckChunk.hpp"
#include "RTC/SCTP/packet/chunks/InitChunk.hpp"
#include "RTC/SCTP/packet/chunks/OperationErrorChunk.hpp"
#include "RTC/SCTP/packet/chunks/ReConfigChunk.hpp"
#include "RTC/SCTP/packet/chunks/SackChunk.hpp"
#include "RTC/SCTP/packet/chunks/ShutdownAckChunk.hpp"
#include "RTC/SCTP/packet/chunks/ShutdownChunk.hpp"
#include "RTC/SCTP/packet/chunks/ShutdownCompleteChunk.hpp"
#include "RTC/SCTP/packet/chunks/UnknownChunk.hpp"
#include "Utils.hpp"
namespace RTC
{
namespace SCTP
{
bool Packet::IsSctp(const uint8_t* , size_t bufferLength)
{
return (
bufferLength >= Packet::CommonHeaderLength && Utils::Byte::IsPaddedTo4Bytes(bufferLength));
}
Packet* Packet::Parse(const uint8_t* buffer, size_t bufferLength)
{
MS_TRACE();
if (!Packet::IsSctp(buffer, bufferLength))
{
MS_WARN_TAG(sctp, "not an SCTP packet");
return nullptr;
}
auto* packet = new Packet(const_cast<uint8_t*>(buffer), bufferLength);
const auto* ptr = buffer;
ptr = packet->GetChunksPointer();
while (ptr < buffer + bufferLength)
{
const size_t chunkMaxBufferLength = bufferLength - (ptr - buffer);
Chunk::ChunkType chunkType;
uint16_t chunkLength;
uint8_t padding;
if (!Chunk::IsChunk(ptr, chunkMaxBufferLength, chunkType, chunkLength, padding))
{
MS_WARN_TAG(sctp, "not an SCTP chunk");
delete packet;
return nullptr;
}
Chunk* chunk{ nullptr };
MS_DEBUG_DEV("parsing SCTP chunk [ptr:%zu, type:%" PRIu8 "]", ptr - buffer, chunkType);
switch (chunkType)
{
case Chunk::ChunkType::DATA:
{
chunk = DataChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding);
break;
}
case Chunk::ChunkType::INIT:
{
chunk = InitChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding);
break;
}
case Chunk::ChunkType::INIT_ACK:
{
chunk = InitAckChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding);
break;
}
case Chunk::ChunkType::SACK:
{
chunk = SackChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding);
break;
}
case Chunk::ChunkType::HEARTBEAT_REQUEST:
{
chunk =
HeartbeatRequestChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding);
break;
}
case Chunk::ChunkType::HEARTBEAT_ACK:
{
chunk = HeartbeatAckChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding);
break;
}
case Chunk::ChunkType::ABORT:
{
chunk =
AbortAssociationChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding);
break;
}
case Chunk::ChunkType::SHUTDOWN:
{
chunk = ShutdownChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding);
break;
}
case Chunk::ChunkType::SHUTDOWN_ACK:
{
chunk = ShutdownAckChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding);
break;
}
case Chunk::ChunkType::OPERATION_ERROR:
{
chunk =
OperationErrorChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding);
break;
}
case Chunk::ChunkType::COOKIE_ECHO:
{
chunk = CookieEchoChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding);
break;
}
case Chunk::ChunkType::COOKIE_ACK:
{
chunk = CookieAckChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding);
break;
}
case Chunk::ChunkType::SHUTDOWN_COMPLETE:
{
chunk =
ShutdownCompleteChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding);
break;
}
case Chunk::ChunkType::FORWARD_TSN:
{
chunk = ForwardTsnChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding);
break;
}
case Chunk::ChunkType::RE_CONFIG:
{
chunk = ReConfigChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding);
break;
}
case Chunk::ChunkType::I_DATA:
{
chunk = IDataChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding);
break;
}
case Chunk::ChunkType::I_FORWARD_TSN:
{
chunk = IForwardTsnChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding);
break;
}
default:
{
chunk = UnknownChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding);
}
}
if (!chunk)
{
delete packet;
return nullptr;
}
packet->chunks.push_back(chunk);
ptr += chunk->GetLength();
}
const size_t computedLength = ptr - buffer;
if (computedLength != bufferLength)
{
MS_WARN_TAG(
sctp,
"computed length (%zu bytes) != buffer length (%zu bytes)",
computedLength,
bufferLength);
delete packet;
return nullptr;
}
packet->SetLength(computedLength);
return packet;
}
Packet* Packet::Factory(uint8_t* buffer, size_t bufferLength)
{
MS_TRACE();
const size_t computedLength = Packet::CommonHeaderLength;
if (bufferLength < computedLength)
{
MS_THROW_TYPE_ERROR("no space for common header");
}
auto* packet = new Packet(buffer, bufferLength);
packet->SetSourcePort(0u);
packet->SetDestinationPort(0u);
packet->SetVerificationTag(0u);
packet->SetChecksum(0u);
return packet;
}
Packet::Packet(uint8_t* buffer, size_t bufferLength) : Serializable(buffer, bufferLength)
{
MS_TRACE();
SetLength(Packet::CommonHeaderLength);
}
Packet::~Packet()
{
MS_TRACE();
for (const auto* chunk : this->chunks)
{
delete chunk;
}
}
void Packet::Dump(int indentation) const
{
MS_TRACE();
MS_DUMP_CLEAN(indentation, "<SCTP::Packet>");
MS_DUMP_CLEAN(indentation, " length: %zu (buffer length: %zu)", GetLength(), GetBufferLength());
MS_DUMP_CLEAN(indentation, " source port: %" PRIu16, GetSourcePort());
MS_DUMP_CLEAN(indentation, " destination port: %" PRIu16, GetDestinationPort());
MS_DUMP_CLEAN(indentation, " verification tag: %" PRIu32, GetVerificationTag());
MS_DUMP_CLEAN(indentation, " checksum: %" PRIu32, GetChecksum());
MS_DUMP_CLEAN(indentation, " chunks count: %zu", GetChunksCount());
MS_DUMP_CLEAN(
indentation, " needs consolidation of chunks: %s", NeedsConsolidation() ? "yes" : "no");
for (const auto* chunk : this->chunks)
{
chunk->Dump(indentation + 1);
}
MS_DUMP_CLEAN(indentation, "</SCTP::Packet>");
}
void Packet::Serialize(uint8_t* buffer, size_t bufferLength)
{
MS_TRACE();
const auto* previousBuffer = GetBuffer();
Serializable::Serialize(buffer, bufferLength);
for (auto* chunk : this->chunks)
{
const size_t offset = chunk->GetBuffer() - previousBuffer;
chunk->SoftSerialize(buffer + offset);
}
}
Packet* Packet::Clone(uint8_t* buffer, size_t bufferLength) const
{
MS_TRACE();
auto* clonedPacket = new Packet(buffer, bufferLength);
Serializable::CloneInto(clonedPacket);
for (auto* chunk : this->chunks)
{
const size_t offset = chunk->GetBuffer() - GetBuffer();
auto* softClonedChunk = chunk->SoftClone(buffer + offset);
clonedPacket->chunks.push_back(softClonedChunk);
}
return clonedPacket;
}
void Packet::SetSourcePort(uint16_t sourcePort)
{
MS_TRACE();
GetHeaderPointer()->sourcePort = htons(sourcePort);
}
void Packet::SetDestinationPort(uint16_t destinationPort)
{
MS_TRACE();
GetHeaderPointer()->destinationPort = htons(destinationPort);
}
void Packet::SetVerificationTag(uint32_t verificationTag)
{
MS_TRACE();
GetHeaderPointer()->verificationTag = htonl(verificationTag);
}
void Packet::SetChecksum(uint32_t checksum)
{
MS_TRACE();
GetHeaderPointer()->checksum = htonl(checksum);
}
void Packet::AddChunk(const Chunk* chunk)
{
MS_TRACE();
AssertDoesNotNeedConsolidation();
const size_t length = GetLength() + chunk->GetLength();
auto* clonedChunk =
chunk->Clone(const_cast<uint8_t*>(GetBuffer()) + GetLength(), chunk->GetLength());
try
{
SetLength(length);
}
catch (const MediaSoupError& error)
{
delete clonedChunk;
throw;
}
this->chunks.push_back(clonedChunk);
}
void Packet::WriteCRC32cChecksum()
{
MS_TRACE();
SetChecksum(0u);
auto crc32c = Utils::Crypto::GetCRC32c(GetBuffer(), GetLength());
SetChecksum(crc32c);
}
bool Packet::ValidateCRC32cChecksum() const
{
MS_TRACE();
auto crc32c = GetChecksum();
GetHeaderPointer()->checksum = 0;
auto computedCrc32c = Utils::Crypto::GetCRC32c(GetBuffer(), GetLength());
GetHeaderPointer()->checksum = htonl(crc32c);
return computedCrc32c == crc32c;
}
void Packet::HandleInPlaceChunk(Chunk* chunk)
{
MS_TRACE();
this->needsConsolidation = true;
chunk->SetConsolidatedListener(
[this, chunk]()
{
try
{
if (chunk->NeedsConsolidation())
{
MS_THROW_ERROR("ongoing chunk needs consolidation");
}
chunk->SetBufferLength(chunk->GetLength());
SetLength(GetLength() + chunk->GetLength());
this->chunks.push_back(chunk);
this->needsConsolidation = false;
}
catch (const MediaSoupError& error)
{
this->needsConsolidation = false;
throw;
}
});
}
void Packet::AssertDoesNotNeedConsolidation() const
{
MS_TRACE();
if (this->needsConsolidation)
{
MS_THROW_ERROR("packet needs consolidation of some ongoing chunk");
}
}
} }