#define MS_CLASS "RTC::RtpPacket"
#include "RTC/RtpPacket.hpp"
#ifdef MS_RTC_LOGGER_RTP
#include "DepLibUV.hpp"
#endif
#include "Logger.hpp"
#include "RTC/Consts.hpp"
#include <cstring>
#include <iterator>
#include <sstream>
namespace RTC
{
RtpPacket* RtpPacket::Parse(const uint8_t* data, size_t len)
{
MS_TRACE();
if (!RtpPacket::IsRtp(data, len))
{
return nullptr;
}
auto* ptr = const_cast<uint8_t*>(data);
auto* header = reinterpret_cast<Header*>(ptr);
ptr += HeaderSize;
size_t csrcListSize{ 0u };
if (header->csrcCount != 0u)
{
csrcListSize = header->csrcCount * sizeof(header->ssrc);
if (len < (ptr - data) + csrcListSize)
{
MS_WARN_TAG(rtp, "not enough space for the announced CSRC list, packet discarded");
return nullptr;
}
ptr += csrcListSize;
}
HeaderExtension* headerExtension{ nullptr };
size_t extensionValueSize{ 0u };
if (header->extension == 1u)
{
if (len < static_cast<size_t>(ptr - data) + 4)
{
MS_WARN_TAG(rtp, "not enough space for the announced header extension, packet discarded");
return nullptr;
}
headerExtension = reinterpret_cast<HeaderExtension*>(ptr);
extensionValueSize = static_cast<size_t>(ntohs(headerExtension->length) * 4);
if (len < (ptr - data) + 4 + extensionValueSize)
{
MS_WARN_TAG(
rtp, "not enough space for the announced header extension value, packet discarded");
return nullptr;
}
ptr += 4 + extensionValueSize;
}
uint8_t* payload = ptr;
size_t payloadLength = len - (ptr - data);
uint8_t payloadPadding{ 0 };
MS_ASSERT(len >= static_cast<size_t>(ptr - data), "payload has negative size");
if (header->padding != 0u)
{
if (payloadLength == 0)
{
MS_WARN_TAG(rtp, "padding bit is set but no space for a padding byte, packet discarded");
return nullptr;
}
payloadPadding = data[len - 1];
if (payloadPadding == 0)
{
MS_WARN_TAG(rtp, "padding byte cannot be 0, packet discarded");
return nullptr;
}
if (payloadLength < size_t{ payloadPadding })
{
MS_WARN_TAG(
rtp,
"number of padding octets is greater than available space for payload, packet "
"discarded");
return nullptr;
}
payloadLength -= size_t{ payloadPadding };
}
MS_ASSERT(
len == HeaderSize + csrcListSize + (headerExtension ? 4 + extensionValueSize : 0) +
payloadLength + size_t{ payloadPadding },
"packet's computed size does not match received size");
return new RtpPacket(header, headerExtension, payload, payloadLength, payloadPadding, len);
}
RtpPacket::RtpPacket(
Header* header,
HeaderExtension* headerExtension,
const uint8_t* payload,
size_t payloadLength,
uint8_t payloadPadding,
size_t size)
: header(header), headerExtension(headerExtension), payload(const_cast<uint8_t*>(payload)),
payloadLength(payloadLength), payloadPadding(payloadPadding), size(size)
{
MS_TRACE();
if (this->header->csrcCount != 0u)
{
this->csrcList = reinterpret_cast<uint8_t*>(header) + HeaderSize;
}
ParseExtensions();
#ifdef MS_RTC_LOGGER_RTP
this->logger.timestamp = DepLibUV::GetTimeMs();
this->logger.recvRtpTimestamp = this->GetTimestamp();
this->logger.recvSeqNumber = this->GetSequenceNumber();
#endif
}
RtpPacket::~RtpPacket()
{
MS_TRACE();
delete[] this->buffer;
}
void RtpPacket::Dump() const
{
MS_TRACE();
MS_DUMP("<RtpPacket>");
MS_DUMP(" padding: %s", this->header->padding ? "true" : "false");
if (HasHeaderExtension())
{
MS_DUMP(
" header extension: id:%" PRIu16 ", length:%zu",
GetHeaderExtensionId(),
GetHeaderExtensionLength());
}
if (HasOneByteExtensions())
{
MS_DUMP(" RFC5285 ext style: One-Byte Header");
}
if (HasTwoBytesExtensions())
{
MS_DUMP(" RFC5285 ext style: Two-Bytes Header");
}
if (HasOneByteExtensions() || HasTwoBytesExtensions())
{
std::vector<std::string> extIds;
std::ostringstream extIdsStream;
if (HasOneByteExtensions())
{
for (const auto& extension : this->oneByteExtensions)
{
if (extension != nullptr)
{
extIds.push_back(std::to_string(extension->id));
}
}
}
else
{
extIds.reserve(this->mapTwoBytesExtensions.size());
for (const auto& kv : this->mapTwoBytesExtensions)
{
extIds.push_back(std::to_string(kv.first));
}
}
if (!extIds.empty())
{
std::copy(
extIds.begin(), extIds.end() - 1, std::ostream_iterator<std::string>(extIdsStream, ","));
extIdsStream << extIds.back();
MS_DUMP(" RFC5285 ext ids: %s", extIdsStream.str().c_str());
}
}
if (this->midExtensionId != 0u)
{
std::string mid;
if (ReadMid(mid))
{
MS_DUMP(" mid: extId:%" PRIu8 ", value:'%s'", this->midExtensionId, mid.c_str());
}
}
if (this->ridExtensionId != 0u)
{
std::string rid;
if (ReadRid(rid))
{
MS_DUMP(" rid: extId:%" PRIu8 ", value:'%s'", this->ridExtensionId, rid.c_str());
}
}
if (this->rridExtensionId != 0u)
{
std::string rid;
if (ReadRid(rid))
{
MS_DUMP(" rrid: extId:%" PRIu8 ", value:'%s'", this->rridExtensionId, rid.c_str());
}
}
if (this->absSendTimeExtensionId != 0u)
{
MS_DUMP(" absSendTime: extId:%" PRIu8, this->absSendTimeExtensionId);
}
if (this->transportWideCc01ExtensionId != 0u)
{
uint16_t wideSeqNumber{ 0 };
if (ReadTransportWideCc01(wideSeqNumber))
{
MS_DUMP(
" transportWideCc01: extId:%" PRIu8 ", value:%" PRIu16,
this->transportWideCc01ExtensionId,
wideSeqNumber);
}
}
if (this->ssrcAudioLevelExtensionId != 0u)
{
uint8_t volume{ 0 };
bool voice{ false };
if (ReadSsrcAudioLevel(volume, voice))
{
MS_DUMP(
" ssrcAudioLevel: extId:%" PRIu8 ", volume:%" PRIu8 ", voice:%s",
this->ssrcAudioLevelExtensionId,
volume,
voice ? "true" : "false");
}
}
if (this->videoOrientationExtensionId != 0u)
{
bool camera{ false };
bool flip{ false };
uint16_t rotation{ 0 };
if (ReadVideoOrientation(camera, flip, rotation))
{
MS_DUMP(
" videoOrientation: extId:%" PRIu8 ", camera:%s, flip:%s, rotation:%" PRIu16,
this->videoOrientationExtensionId,
camera ? "true" : "false",
flip ? "true" : "false",
rotation);
}
}
if (this->playoutDelayExtensionId != 0u)
{
uint16_t minDelay{ 0 };
uint16_t maxDelay{ 0 };
if (ReadPlayoutDelay(minDelay, maxDelay))
{
MS_DUMP(
" playoutDelay: extId:%" PRIu8 ", minDelay:%" PRIu16 ", maxDelay:%" PRIu16,
this->videoOrientationExtensionId,
minDelay,
maxDelay);
}
}
MS_DUMP(" csrc count: %" PRIu8, this->header->csrcCount);
MS_DUMP(" marker: %s", HasMarker() ? "true" : "false");
MS_DUMP(" payload type: %" PRIu8, GetPayloadType());
MS_DUMP(" sequence number: %" PRIu16, GetSequenceNumber());
MS_DUMP(" timestamp: %" PRIu32, GetTimestamp());
MS_DUMP(" ssrc: %" PRIu32, GetSsrc());
MS_DUMP(" payload size: %zu bytes", GetPayloadLength());
if (this->header->padding != 0u)
{
MS_DUMP(" padding size: %" PRIu8 " bytes", this->payloadPadding);
}
MS_DUMP(" packet size: %zu bytes", GetSize());
MS_DUMP(" spatial layer: %" PRIu8, GetSpatialLayer());
MS_DUMP(" temporal layer: %" PRIu8, GetTemporalLayer());
MS_DUMP("</RtpPacket>");
}
flatbuffers::Offset<FBS::RtpPacket::Dump> RtpPacket::FillBuffer(
flatbuffers::FlatBufferBuilder& builder) const
{
std::string mid;
if (this->midExtensionId != 0u)
{
ReadMid(mid);
}
std::string rid;
if (this->ridExtensionId != 0u)
{
ReadRid(rid);
}
std::string rrid;
if (this->rridExtensionId != 0u)
{
ReadRid(rrid);
}
uint16_t wideSequenceNumber{ 0 };
bool wideSequenceNumberSet = false;
if (this->transportWideCc01ExtensionId != 0u)
{
wideSequenceNumberSet = true;
ReadTransportWideCc01(wideSequenceNumber);
}
return FBS::RtpPacket::CreateDumpDirect(
builder,
this->GetPayloadType(),
this->GetSequenceNumber(),
this->GetTimestamp(),
this->HasMarker(),
this->GetSsrc(),
this->IsKeyFrame(),
this->GetSize(),
this->GetPayloadLength(),
this->GetSpatialLayer(),
this->GetTemporalLayer(),
mid.empty() ? nullptr : mid.c_str(),
rid.empty() ? nullptr : rid.c_str(),
rrid.empty() ? nullptr : rrid.c_str(),
wideSequenceNumberSet ? flatbuffers::Optional<uint16_t>(wideSequenceNumber)
: flatbuffers::nullopt);
}
void RtpPacket::SetExtensions(uint8_t type, const std::vector<GenericExtension>& extensions)
{
MS_ASSERT(type == 1u || type == 2u, "type must be 1 or 2");
this->midExtensionId = 0u;
this->ridExtensionId = 0u;
this->rridExtensionId = 0u;
this->absSendTimeExtensionId = 0u;
this->transportWideCc01ExtensionId = 0u;
this->ssrcAudioLevelExtensionId = 0u;
this->videoOrientationExtensionId = 0u;
this->playoutDelayExtensionId = 0u;
this->dependencyDescriptorExtensionId = 0u;
std::fill(std::begin(this->oneByteExtensions), std::end(this->oneByteExtensions), nullptr);
this->mapTwoBytesExtensions.clear();
if (type == 1u && HasOneByteExtensions())
{
}
else if (type == 2u && HasTwoBytesExtensions())
{
}
else if (this->headerExtension)
{
if (type == 1u)
{
this->headerExtension->id = uint16_t{ htons(0xBEDE) };
}
else if (type == 2u)
{
this->headerExtension->id = uint16_t{ htons(0b0001000000000000) };
}
}
size_t extensionsTotalSize{ 0 };
for (const auto& extension : extensions)
{
if (type == 1u)
{
if (extension.id == 0 || extension.id > 14 || extension.len == 0 || extension.len > 16)
{
continue;
}
extensionsTotalSize += (1 + extension.len);
}
else if (type == 2u)
{
if (extension.id == 0)
{
continue;
}
extensionsTotalSize += (2 + extension.len);
}
}
auto paddedExtensionsTotalSize = Utils::Byte::PadTo4Bytes(extensionsTotalSize);
const size_t padding = paddedExtensionsTotalSize - extensionsTotalSize;
extensionsTotalSize = paddedExtensionsTotalSize;
int16_t shift{ 0 };
if (this->headerExtension)
{
shift = static_cast<int16_t>(extensionsTotalSize - GetHeaderExtensionLength());
}
else
{
shift = 4 + static_cast<int16_t>(extensionsTotalSize);
}
if (this->headerExtension && shift != 0)
{
std::memmove(this->payload + shift, this->payload, this->payloadLength + this->payloadPadding);
this->payload += shift;
this->size += shift;
this->headerExtension->length = htons(extensionsTotalSize / 4);
}
else if (!this->headerExtension)
{
this->header->extension = 1u;
this->headerExtension = reinterpret_cast<HeaderExtension*>(this->payload);
std::memmove(this->payload + shift, this->payload, this->payloadLength + this->payloadPadding);
this->payload += shift;
this->size += shift;
if (type == 1u)
{
this->headerExtension->id = uint16_t{ htons(0xBEDE) };
}
else if (type == 2u)
{
this->headerExtension->id = uint16_t{ htons(0b0001000000000000) };
}
this->headerExtension->length = htons(extensionsTotalSize / 4);
}
uint8_t* ptr = this->headerExtension->value;
for (const auto& extension : extensions)
{
if (type == 1u)
{
if (extension.id == 0 || extension.id > 14 || extension.len == 0 || extension.len > 16)
{
continue;
}
this->oneByteExtensions[extension.id - 1] = reinterpret_cast<OneByteExtension*>(ptr);
*ptr = (extension.id << 4) | ((extension.len - 1) & 0x0F);
++ptr;
std::memmove(ptr, extension.value, extension.len);
ptr += extension.len;
}
else if (type == 2u)
{
if (extension.id == 0)
{
continue;
}
this->mapTwoBytesExtensions[extension.id] = reinterpret_cast<TwoBytesExtension*>(ptr);
*ptr = extension.id;
++ptr;
*ptr = extension.len;
++ptr;
std::memmove(ptr, extension.value, extension.len);
ptr += extension.len;
}
}
for (size_t i = 0; i < padding; ++i)
{
*ptr = 0u;
++ptr;
}
MS_ASSERT(ptr == this->payload, "wrong ptr calculation");
}
void RtpPacket::UpdateMid(const std::string& mid)
{
MS_TRACE();
uint8_t extenLen;
uint8_t* extenValue = GetExtension(this->midExtensionId, extenLen);
if (!extenValue)
{
return;
}
const size_t midLen = mid.length();
if (midLen > RTC::Consts::MidRtpExtensionMaxLength)
{
MS_ERROR(
"no enough space for MID value [MidMaxLength:%" PRIu8 ", mid:'%s']",
RTC::Consts::MidRtpExtensionMaxLength,
mid.c_str());
return;
}
std::memcpy(extenValue, mid.c_str(), midLen);
SetExtensionLength(this->midExtensionId, midLen);
}
bool RtpPacket::SetExtensionLength(uint8_t id, uint8_t len)
{
MS_TRACE();
if (len == 0u)
{
MS_ERROR("cannot set extension length to 0");
return false;
}
if (id == 0u)
{
return false;
}
else if (HasOneByteExtensions())
{
auto* extension = this->oneByteExtensions[id - 1];
if (!extension)
{
return false;
}
auto currentLen = extension->len + 1;
if (len < currentLen)
{
std::memset(extension->value + len, 0, currentLen - len);
}
extension->len = len - 1;
return true;
}
else if (HasTwoBytesExtensions())
{
auto it = this->mapTwoBytesExtensions.find(id);
if (it == this->mapTwoBytesExtensions.end())
{
return false;
}
auto* extension = it->second;
auto currentLen = extension->len;
if (len < currentLen)
{
std::memset(extension->value + len, 0, currentLen - len);
}
extension->len = len;
return true;
}
else
{
return false;
}
}
void RtpPacket::SetPayloadLength(size_t length)
{
MS_TRACE();
this->size -= this->payloadLength;
this->payloadLength = length;
this->size += this->payloadLength;
if (this->payloadPadding != 0u)
{
SetPayloadPaddingFlag(false);
this->size -= size_t{ this->payloadPadding };
this->payloadPadding = 0u;
}
}
RtpPacket* RtpPacket::Clone() const
{
MS_TRACE();
auto* buffer = new uint8_t[RTC::Consts::MtuSize + 100];
auto* ptr = const_cast<uint8_t*>(buffer);
size_t numBytes{ 0 };
numBytes = HeaderSize;
std::memcpy(ptr, GetData(), numBytes);
auto* newHeader = reinterpret_cast<Header*>(ptr);
ptr += numBytes;
if (this->csrcList != nullptr)
{
numBytes = this->header->csrcCount * sizeof(this->header->ssrc);
std::memcpy(ptr, this->csrcList, numBytes);
ptr += numBytes;
}
HeaderExtension* newHeaderExtension{ nullptr };
if (this->headerExtension != nullptr)
{
numBytes = 4 + GetHeaderExtensionLength();
std::memcpy(ptr, this->headerExtension, numBytes);
newHeaderExtension = reinterpret_cast<HeaderExtension*>(ptr);
ptr += numBytes;
}
uint8_t* newPayload{ ptr };
if (this->payloadLength != 0u)
{
numBytes = this->payloadLength;
std::memcpy(ptr, this->payload, numBytes);
ptr += numBytes;
}
if (this->payloadPadding != 0u)
{
*(ptr + static_cast<size_t>(this->payloadPadding) - 1) = this->payloadPadding;
ptr += size_t{ this->payloadPadding };
}
MS_ASSERT(static_cast<size_t>(ptr - buffer) == this->size, "ptr - buffer == this->size");
auto* packet = new RtpPacket(
newHeader, newHeaderExtension, newPayload, this->payloadLength, this->payloadPadding, this->size);
packet->midExtensionId = this->midExtensionId;
packet->ridExtensionId = this->ridExtensionId;
packet->rridExtensionId = this->rridExtensionId;
packet->absSendTimeExtensionId = this->absSendTimeExtensionId;
packet->transportWideCc01ExtensionId = this->transportWideCc01ExtensionId;
packet->ssrcAudioLevelExtensionId = this->ssrcAudioLevelExtensionId;
packet->videoOrientationExtensionId = this->videoOrientationExtensionId;
packet->playoutDelayExtensionId = this->playoutDelayExtensionId;
packet->dependencyDescriptorExtensionId = this->dependencyDescriptorExtensionId;
packet->payloadDescriptorHandler = this->payloadDescriptorHandler;
packet->buffer = buffer;
return packet;
}
void RtpPacket::RtxEncode(uint8_t payloadType, uint32_t ssrc, uint16_t seq)
{
MS_TRACE();
SetPayloadType(payloadType);
SetSsrc(ssrc);
std::memmove(this->payload + 2, this->payload, this->payloadLength);
Utils::Byte::Set2Bytes(this->payload, 0, GetSequenceNumber());
SetSequenceNumber(seq);
this->payloadLength += 2u;
this->size += 2u;
if (this->payloadPadding != 0u)
{
SetPayloadPaddingFlag(false);
this->size -= size_t{ this->payloadPadding };
this->payloadPadding = 0u;
}
}
bool RtpPacket::RtxDecode(uint8_t payloadType, uint32_t ssrc)
{
MS_TRACE();
if (this->payloadLength < 2u)
{
return false;
}
SetPayloadType(payloadType);
SetSequenceNumber(Utils::Byte::Get2Bytes(this->payload, 0));
SetSsrc(ssrc);
std::memmove(this->payload, this->payload + 2, this->payloadLength - 2);
this->payloadLength -= 2u;
this->size -= 2u;
if (this->payloadPadding != 0u)
{
SetPayloadPaddingFlag(false);
this->size -= size_t{ this->payloadPadding };
this->payloadPadding = 0u;
}
return true;
}
bool RtpPacket::ProcessPayload(RTC::Codecs::EncodingContext* context, bool& marker)
{
MS_TRACE();
if (!this->payloadDescriptorHandler)
{
return true;
}
return this->payloadDescriptorHandler->Process(context, this, marker);
}
std::unique_ptr<Codecs::PayloadDescriptor::Encoder> RtpPacket::GetPayloadEncoder()
{
MS_TRACE();
if (!this->payloadDescriptorHandler)
{
return nullptr;
}
return this->payloadDescriptorHandler->GetEncoder();
}
void RtpPacket::EncodePayload(Codecs::PayloadDescriptor::Encoder* encoder)
{
MS_TRACE();
if (!this->payloadDescriptorHandler)
{
return;
}
this->payloadDescriptorHandler->Encode(this, encoder);
}
void RtpPacket::RestorePayload()
{
MS_TRACE();
if (!this->payloadDescriptorHandler)
{
return;
}
this->payloadDescriptorHandler->Restore(this);
}
void RtpPacket::ShiftPayload(size_t payloadOffset, size_t shift, bool expand)
{
MS_TRACE();
if (shift == 0u)
{
return;
}
MS_ASSERT(payloadOffset < this->payloadLength, "payload offset bigger than payload size");
if (!expand)
{
MS_ASSERT(shift <= (this->payloadLength - payloadOffset), "shift too big");
}
uint8_t* payloadOffsetPtr = this->payload + payloadOffset;
size_t shiftedLen{ 0 };
if (expand)
{
shiftedLen = this->payloadLength - payloadOffset;
std::memmove(payloadOffsetPtr + shift, payloadOffsetPtr, shiftedLen);
this->payloadLength += shift;
this->size += shift;
}
else
{
shiftedLen = this->payloadLength - payloadOffset - shift;
std::memmove(payloadOffsetPtr, payloadOffsetPtr + shift, shiftedLen);
this->payloadLength -= shift;
this->size -= shift;
}
if (this->payloadPadding != 0u)
{
SetPayloadPaddingFlag(false);
this->size -= size_t{ this->payloadPadding };
this->payloadPadding = 0u;
}
}
void RtpPacket::ParseExtensions()
{
MS_TRACE();
if (HasOneByteExtensions())
{
std::fill(std::begin(this->oneByteExtensions), std::end(this->oneByteExtensions), nullptr);
uint8_t* extensionStart = reinterpret_cast<uint8_t*>(this->headerExtension) + 4;
uint8_t* extensionEnd = extensionStart + GetHeaderExtensionLength();
uint8_t* ptr = extensionStart;
while (ptr < extensionEnd)
{
const uint8_t id = (*ptr & 0xF0) >> 4;
const size_t len = static_cast<size_t>(*ptr & 0x0F) + 1;
if (id == 15u)
{
break;
}
if (id != 0u)
{
if (ptr + 1 + len > extensionEnd)
{
MS_WARN_TAG(
rtp, "not enough space for the announced One-Byte header extension element value");
break;
}
this->oneByteExtensions[id - 1] = reinterpret_cast<OneByteExtension*>(ptr);
ptr += (1 + len);
}
else
{
++ptr;
}
while ((ptr < extensionEnd) && (*ptr == 0))
{
++ptr;
}
}
}
else if (HasTwoBytesExtensions())
{
this->mapTwoBytesExtensions.clear();
uint8_t* extensionStart = reinterpret_cast<uint8_t*>(this->headerExtension) + 4;
uint8_t* extensionEnd = extensionStart + GetHeaderExtensionLength();
uint8_t* ptr = extensionStart;
while (ptr + 1 < extensionEnd)
{
const uint8_t id = *ptr;
const uint8_t len = *(ptr + 1);
if (id != 0u)
{
if (ptr + 2 + len > extensionEnd)
{
MS_WARN_TAG(
rtp, "not enough space for the announced Two-Bytes header extension element value");
break;
}
this->mapTwoBytesExtensions[id] = reinterpret_cast<TwoBytesExtension*>(ptr);
ptr += (2 + len);
}
else
{
++ptr;
}
while ((ptr < extensionEnd) && (*ptr == 0))
{
++ptr;
}
}
}
}
}