#define MS_CLASS "RTC::RTCP::Sdes"
#include "RTC/RTCP/Sdes.hpp"
#include "Logger.hpp"
#include "Utils.hpp"
#include <cstring>
namespace RTC
{
namespace RTCP
{
const absl::flat_hash_map<SdesItem::Type, std::string> SdesItem::Type2String =
{
{ SdesItem::Type::END, "END" },
{ SdesItem::Type::CNAME, "CNAME" },
{ SdesItem::Type::NAME, "NAME" },
{ SdesItem::Type::EMAIL, "EMAIL" },
{ SdesItem::Type::PHONE, "PHONE" },
{ SdesItem::Type::LOC, "LOC" },
{ SdesItem::Type::TOOL, "TOOL" },
{ SdesItem::Type::NOTE, "NOTE" },
{ SdesItem::Type::PRIV, "PRIV" }
};
SdesItem* SdesItem::Parse(const uint8_t* data, size_t len)
{
MS_TRACE();
auto* header = const_cast<Header*>(reinterpret_cast<const Header*>(data));
if (len > 0 && header->type == SdesItem::Type::END)
{
return nullptr;
}
if (len < HeaderSize || len < HeaderSize + header->length)
{
MS_WARN_TAG(rtcp, "not enough space for SDES item, discarded");
return nullptr;
}
return new SdesItem(header);
}
const std::string& SdesItem::TypeToString(SdesItem::Type type)
{
static const std::string Unknown("UNKNOWN");
auto it = SdesItem::Type2String.find(type);
if (it == SdesItem::Type2String.end())
{
return Unknown;
}
return it->second;
}
SdesItem::SdesItem(SdesItem::Type type, size_t len, const char* value)
{
MS_TRACE();
this->raw.reset(new uint8_t[2 + len]);
this->header = reinterpret_cast<Header*>(this->raw.get());
this->header->type = type;
this->header->length = len;
std::memcpy(this->header->value, value, this->header->length);
}
void SdesItem::Dump(int indentation) const
{
MS_TRACE();
MS_DUMP_CLEAN(indentation, "<SdesItem>");
MS_DUMP_CLEAN(indentation, " type: %s", SdesItem::TypeToString(this->GetType()).c_str());
MS_DUMP_CLEAN(indentation, " length: %" PRIu8, this->header->length);
MS_DUMP_CLEAN(indentation, " value: %.*s", this->header->length, this->header->value);
MS_DUMP_CLEAN(indentation, "</SdesItem>");
}
size_t SdesItem::Serialize(uint8_t* buffer)
{
MS_TRACE();
std::memcpy(buffer, this->header, 2);
std::memcpy(buffer + 2, this->header->value, this->header->length);
return 2 + this->header->length;
}
SdesChunk* SdesChunk::Parse(const uint8_t* data, size_t len)
{
MS_TRACE();
if (len < 4u )
{
MS_WARN_TAG(rtcp, "not enough space for SDES chunk, discarded");
return nullptr;
}
const uint32_t ssrc = Utils::Byte::Get4Bytes(data, 0);
std::unique_ptr<SdesChunk> chunk(new SdesChunk(ssrc));
size_t offset{ 4u };
size_t chunkLength{ 4u };
while (len > offset)
{
auto* item = SdesItem::Parse(data + offset, len - offset);
if (!item)
{
break;
}
chunk->AddItem(item);
chunkLength += item->GetSize();
offset += item->GetSize();
}
if (offset == len || (offset < len && Utils::Byte::Get1Byte(data, offset) != 0u))
{
MS_WARN_TAG(rtcp, "invalid SDES chunk (missing mandatory null octet), discarded");
return nullptr;
}
++chunkLength;
++offset;
auto neededAdditionalNullOctets = Utils::Byte::PadTo4Bytes(static_cast<uint16_t>(chunkLength)) -
static_cast<uint16_t>(chunkLength);
uint16_t foundAdditionalNullOctets{ 0u };
for (uint16_t i{ 0u }; len > offset && i < neededAdditionalNullOctets; ++i)
{
if (Utils::Byte::Get1Byte(data, offset) != 0u)
{
MS_WARN_TAG(
rtcp,
"invalid SDES chunk (missing additional null octets [needed:%" PRIu16 ", found:%" PRIu16
"]), discarded",
neededAdditionalNullOctets,
foundAdditionalNullOctets);
return nullptr;
}
++foundAdditionalNullOctets;
++chunkLength;
++offset;
}
if (foundAdditionalNullOctets != neededAdditionalNullOctets)
{
MS_WARN_TAG(
rtcp,
"invalid SDES chunk (missing additional null octets [needed:%" PRIu16 ", found:%" PRIu16
"]), discarded",
neededAdditionalNullOctets,
foundAdditionalNullOctets);
return nullptr;
}
return chunk.release();
}
size_t SdesChunk::Serialize(uint8_t* buffer)
{
MS_TRACE();
Utils::Byte::Set4Bytes(buffer, 0, this->ssrc);
size_t offset{ 4u };
for (auto* item : this->items)
{
offset += item->Serialize(buffer + offset);
}
buffer[offset] = 0;
++offset;
const size_t padding = (-offset) & 3;
for (size_t i{ 0u }; i < padding; ++i)
{
buffer[offset + i] = 0;
}
return offset + padding;
}
void SdesChunk::Dump(int indentation) const
{
MS_TRACE();
MS_DUMP_CLEAN(indentation, "<SdesChunk>");
MS_DUMP_CLEAN(indentation, " ssrc: %" PRIu32, this->ssrc);
for (auto* item : this->items)
{
item->Dump(indentation + 1);
}
MS_DUMP_CLEAN(indentation, "</SdesChunk>");
}
size_t SdesPacket::maxChunksPerPacket = 31;
SdesPacket* SdesPacket::Parse(const uint8_t* data, size_t len)
{
MS_TRACE();
auto* header = const_cast<CommonHeader*>(reinterpret_cast<const CommonHeader*>(data));
std::unique_ptr<SdesPacket> packet(new SdesPacket(header));
size_t offset = Packet::CommonHeaderSize;
uint8_t count = header->count;
while (count-- && (len > offset))
{
auto* chunk = SdesChunk::Parse(data + offset, len - offset);
if (chunk != nullptr)
{
packet->AddChunk(chunk);
offset += chunk->GetSize();
}
else
{
break;
}
}
if (packet->GetCount() != header->count)
{
MS_WARN_TAG(rtcp, "RTCP count value doesn't match found number of chunks, discarded");
return nullptr;
}
return packet.release();
}
size_t SdesPacket::Serialize(uint8_t* buffer)
{
MS_TRACE();
size_t offset = 0;
size_t length = 0;
uint8_t* header = { nullptr };
for (size_t i{ 0u }; i < this->GetCount(); ++i)
{
if (i % SdesPacket::maxChunksPerPacket == 0)
{
header = buffer + offset;
offset += Packet::Serialize(buffer + offset);
length = Packet::CommonHeaderSize;
}
auto chunkSize = chunks[i]->Serialize(buffer + offset);
offset += chunkSize;
length += chunkSize;
reinterpret_cast<Packet::CommonHeader*>(header)->count =
static_cast<uint8_t>((i % SdesPacket::maxChunksPerPacket) + 1);
reinterpret_cast<Packet::CommonHeader*>(header)->length = htons((length / 4) - 1);
}
return offset;
}
void SdesPacket::Dump(int indentation) const
{
MS_TRACE();
MS_DUMP_CLEAN(indentation, "<SdesPacket>");
for (auto* chunk : this->chunks)
{
chunk->Dump(indentation + 1);
}
MS_DUMP_CLEAN(indentation, "</SdesPacket>");
}
} }