diff --git a/src/rtcp/byte_io.h b/src/rtcp/byte_io.h new file mode 100644 index 0000000..388a1e2 --- /dev/null +++ b/src/rtcp/byte_io.h @@ -0,0 +1,394 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef MODULES_RTP_RTCP_SOURCE_BYTE_IO_H_ +#define MODULES_RTP_RTCP_SOURCE_BYTE_IO_H_ + +// This file contains classes for reading and writing integer types from/to +// byte array representations. Signed/unsigned, partial (whole byte) sizes, +// and big/little endian byte order is all supported. +// +// Usage examples: +// +// uint8_t* buffer = ...; +// +// // Read an unsigned 4 byte integer in big endian format +// uint32_t val = ByteReader::ReadBigEndian(buffer); +// +// // Read a signed 24-bit (3 byte) integer in little endian format +// int32_t val = ByteReader::ReadLittle(buffer); +// +// // Write an unsigned 8 byte integer in little endian format +// ByteWriter::WriteLittleEndian(buffer, val); +// +// Write an unsigned 40-bit (5 byte) integer in big endian format +// ByteWriter::WriteBigEndian(buffer, val); +// +// These classes are implemented as recursive templetizations, intended to make +// it easy for the compiler to completely inline the reading/writing. + +#include + +#include + +// According to ISO C standard ISO/IEC 9899, section 6.2.6.2 (2), the three +// representations of signed integers allowed are two's complement, one's +// complement and sign/magnitude. We can detect which is used by looking at +// the two last bits of -1, which will be 11 in two's complement, 10 in one's +// complement and 01 in sign/magnitude. +// TODO(sprang): In the unlikely event that we actually need to support a +// platform that doesn't use two's complement, implement conversion to/from +// wire format. + +// Assume the if any one signed integer type is two's complement, then all +// other will be too. +static_assert( + (-1 & 0x03) == 0x03, + "Only two's complement representation of signed integers supported."); + +// Plain const char* won't work for static_assert, use #define instead. +#define kSizeErrorMsg "Byte size must be less than or equal to data type size." + +// Utility class for getting the unsigned equivalent of a signed type. +template +struct UnsignedOf; + +// Class for reading integers from a sequence of bytes. +// T = type of integer, B = bytes to read, is_signed = true if signed integer. +// If is_signed is true and B < sizeof(T), sign extension might be needed. +template ::is_signed> +class ByteReader; + +// Specialization of ByteReader for unsigned types. +template +class ByteReader { + public: + static T ReadBigEndian(const uint8_t* data) { + static_assert(B <= sizeof(T), kSizeErrorMsg); + return InternalReadBigEndian(data); + } + + static T ReadLittleEndian(const uint8_t* data) { + static_assert(B <= sizeof(T), kSizeErrorMsg); + return InternalReadLittleEndian(data); + } + + private: + static T InternalReadBigEndian(const uint8_t* data) { + T val(0); + for (unsigned int i = 0; i < B; ++i) + val |= static_cast(data[i]) << ((B - 1 - i) * 8); + return val; + } + + static T InternalReadLittleEndian(const uint8_t* data) { + T val(0); + for (unsigned int i = 0; i < B; ++i) + val |= static_cast(data[i]) << (i * 8); + return val; + } +}; + +// Specialization of ByteReader for signed types. +template +class ByteReader { + public: + typedef typename UnsignedOf::Type U; + + static T ReadBigEndian(const uint8_t* data) { + U unsigned_val = ByteReader::ReadBigEndian(data); + if (B < sizeof(T)) unsigned_val = SignExtend(unsigned_val); + return ReinterpretAsSigned(unsigned_val); + } + + static T ReadLittleEndian(const uint8_t* data) { + U unsigned_val = ByteReader::ReadLittleEndian(data); + if (B < sizeof(T)) unsigned_val = SignExtend(unsigned_val); + return ReinterpretAsSigned(unsigned_val); + } + + private: + // As a hack to avoid implementation-specific or undefined behavior when + // bit-shifting or casting signed integers, read as a signed equivalent + // instead and convert to signed. This is safe since we have asserted that + // two's complement for is used. + static T ReinterpretAsSigned(U unsigned_val) { + // An unsigned value with only the highest order bit set (ex 0x80). + const U kUnsignedHighestBitMask = static_cast(1) + << ((sizeof(U) * 8) - 1); + // A signed value with only the highest bit set. Since this is two's + // complement form, we can use the min value from std::numeric_limits. + const T kSignedHighestBitMask = std::numeric_limits::min(); + + T val; + if ((unsigned_val & kUnsignedHighestBitMask) != 0) { + // Casting is only safe when unsigned value can be represented in the + // signed target type, so mask out highest bit and mask it back manually. + val = static_cast(unsigned_val & ~kUnsignedHighestBitMask); + val |= kSignedHighestBitMask; + } else { + val = static_cast(unsigned_val); + } + return val; + } + + // If number of bytes is less than native data type (eg 24 bit, in int32_t), + // and the most significant bit of the actual data is set, we must sign + // extend the remaining byte(s) with ones so that the correct negative + // number is retained. + // Ex: 0x810A0B -> 0xFF810A0B, but 0x710A0B -> 0x00710A0B + static U SignExtend(const U val) { + const uint8_t kMsb = static_cast(val >> ((B - 1) * 8)); + if ((kMsb & 0x80) != 0) { + // Create a mask where all bits used by the B bytes are set to one, + // for instance 0x00FFFFFF for B = 3. Bit-wise invert that mask (to + // (0xFF000000 in the example above) and add it to the input value. + // The "B % sizeof(T)" is a workaround to undefined values warnings for + // B == sizeof(T), in which case this code won't be called anyway. + const U kUsedBitsMask = (1 << ((B % sizeof(T)) * 8)) - 1; + return ~kUsedBitsMask | val; + } + return val; + } +}; + +// Class for writing integers to a sequence of bytes +// T = type of integer, B = bytes to write +template ::is_signed> +class ByteWriter; + +// Specialization of ByteWriter for unsigned types. +template +class ByteWriter { + public: + static void WriteBigEndian(uint8_t* data, T val) { + static_assert(B <= sizeof(T), kSizeErrorMsg); + for (unsigned int i = 0; i < B; ++i) { + data[i] = val >> ((B - 1 - i) * 8); + } + } + + static void WriteLittleEndian(uint8_t* data, T val) { + static_assert(B <= sizeof(T), kSizeErrorMsg); + for (unsigned int i = 0; i < B; ++i) { + data[i] = val >> (i * 8); + } + } +}; + +// Specialization of ByteWriter for signed types. +template +class ByteWriter { + public: + typedef typename UnsignedOf::Type U; + + static void WriteBigEndian(uint8_t* data, T val) { + ByteWriter::WriteBigEndian(data, ReinterpretAsUnsigned(val)); + } + + static void WriteLittleEndian(uint8_t* data, T val) { + ByteWriter::WriteLittleEndian(data, + ReinterpretAsUnsigned(val)); + } + + private: + static U ReinterpretAsUnsigned(T val) { + // According to ISO C standard ISO/IEC 9899, section 6.3.1.3 (1, 2) a + // conversion from signed to unsigned keeps the value if the new type can + // represent it, and otherwise adds one more than the max value of T until + // the value is in range. For two's complement, this fortunately means + // that the bit-wise value will be intact. Thus, since we have asserted that + // two's complement form is actually used, a simple cast is sufficient. + return static_cast(val); + } +}; + +// ----- Below follows specializations of UnsignedOf utility class ----- + +template <> +struct UnsignedOf { + typedef uint8_t Type; +}; +template <> +struct UnsignedOf { + typedef uint16_t Type; +}; +template <> +struct UnsignedOf { + typedef uint32_t Type; +}; +template <> +struct UnsignedOf { + typedef uint64_t Type; +}; + +// ----- Below follows specializations for unsigned, B in { 1, 2, 4, 8 } ----- + +// TODO(sprang): Check if these actually help or if generic cases will be +// unrolled to and optimized to similar performance. + +// Specializations for single bytes +template +class ByteReader { + public: + static T ReadBigEndian(const uint8_t* data) { + static_assert(sizeof(T) == 1, kSizeErrorMsg); + return data[0]; + } + + static T ReadLittleEndian(const uint8_t* data) { + static_assert(sizeof(T) == 1, kSizeErrorMsg); + return data[0]; + } +}; + +template +class ByteWriter { + public: + static void WriteBigEndian(uint8_t* data, T val) { + static_assert(sizeof(T) == 1, kSizeErrorMsg); + data[0] = val; + } + + static void WriteLittleEndian(uint8_t* data, T val) { + static_assert(sizeof(T) == 1, kSizeErrorMsg); + data[0] = val; + } +}; + +// Specializations for two byte words +template +class ByteReader { + public: + static T ReadBigEndian(const uint8_t* data) { + static_assert(sizeof(T) >= 2, kSizeErrorMsg); + return (data[0] << 8) | data[1]; + } + + static T ReadLittleEndian(const uint8_t* data) { + static_assert(sizeof(T) >= 2, kSizeErrorMsg); + return data[0] | (data[1] << 8); + } +}; + +template +class ByteWriter { + public: + static void WriteBigEndian(uint8_t* data, T val) { + static_assert(sizeof(T) >= 2, kSizeErrorMsg); + data[0] = val >> 8; + data[1] = val; + } + + static void WriteLittleEndian(uint8_t* data, T val) { + static_assert(sizeof(T) >= 2, kSizeErrorMsg); + data[0] = val; + data[1] = val >> 8; + } +}; + +// Specializations for four byte words. +template +class ByteReader { + public: + static T ReadBigEndian(const uint8_t* data) { + static_assert(sizeof(T) >= 4, kSizeErrorMsg); + return (Get(data, 0) << 24) | (Get(data, 1) << 16) | (Get(data, 2) << 8) | + Get(data, 3); + } + + static T ReadLittleEndian(const uint8_t* data) { + static_assert(sizeof(T) >= 4, kSizeErrorMsg); + return Get(data, 0) | (Get(data, 1) << 8) | (Get(data, 2) << 16) | + (Get(data, 3) << 24); + } + + private: + inline static T Get(const uint8_t* data, unsigned int index) { + return static_cast(data[index]); + } +}; + +// Specializations for four byte words. +template +class ByteWriter { + public: + static void WriteBigEndian(uint8_t* data, T val) { + static_assert(sizeof(T) >= 4, kSizeErrorMsg); + data[0] = val >> 24; + data[1] = val >> 16; + data[2] = val >> 8; + data[3] = val; + } + + static void WriteLittleEndian(uint8_t* data, T val) { + static_assert(sizeof(T) >= 4, kSizeErrorMsg); + data[0] = val; + data[1] = val >> 8; + data[2] = val >> 16; + data[3] = val >> 24; + } +}; + +// Specializations for eight byte words. +template +class ByteReader { + public: + static T ReadBigEndian(const uint8_t* data) { + static_assert(sizeof(T) >= 8, kSizeErrorMsg); + return (Get(data, 0) << 56) | (Get(data, 1) << 48) | (Get(data, 2) << 40) | + (Get(data, 3) << 32) | (Get(data, 4) << 24) | (Get(data, 5) << 16) | + (Get(data, 6) << 8) | Get(data, 7); + } + + static T ReadLittleEndian(const uint8_t* data) { + static_assert(sizeof(T) >= 8, kSizeErrorMsg); + return Get(data, 0) | (Get(data, 1) << 8) | (Get(data, 2) << 16) | + (Get(data, 3) << 24) | (Get(data, 4) << 32) | (Get(data, 5) << 40) | + (Get(data, 6) << 48) | (Get(data, 7) << 56); + } + + private: + inline static T Get(const uint8_t* data, unsigned int index) { + return static_cast(data[index]); + } +}; + +template +class ByteWriter { + public: + static void WriteBigEndian(uint8_t* data, T val) { + static_assert(sizeof(T) >= 8, kSizeErrorMsg); + data[0] = val >> 56; + data[1] = val >> 48; + data[2] = val >> 40; + data[3] = val >> 32; + data[4] = val >> 24; + data[5] = val >> 16; + data[6] = val >> 8; + data[7] = val; + } + + static void WriteLittleEndian(uint8_t* data, T val) { + static_assert(sizeof(T) >= 8, kSizeErrorMsg); + data[0] = val; + data[1] = val >> 8; + data[2] = val >> 16; + data[3] = val >> 24; + data[4] = val >> 32; + data[5] = val >> 40; + data[6] = val >> 48; + data[7] = val >> 56; + } +}; + +#endif // MODULES_RTP_RTCP_SOURCE_BYTE_IO_H_ diff --git a/src/rtcp/rtcp_packet.cpp b/src/rtcp/rtcp_packet.cpp index 37d5e56..141648f 100644 --- a/src/rtcp/rtcp_packet.cpp +++ b/src/rtcp/rtcp_packet.cpp @@ -4,12 +4,12 @@ #define IP_PACKET_SIZE 1500 // we assume ethernet -bool RtcpPacket::OnBufferFull(std::vector& packet, +bool RtcpPacket::OnBufferFull(uint8_t* packet, size_t* index, PacketReadyCallback callback) const { - if (packet.empty()) { + if (*index == 0) { return false; } - callback(packet); + callback(packet, *index); return true; } diff --git a/src/rtcp/rtcp_packet.h b/src/rtcp/rtcp_packet.h index e1341b6..4e055cd 100644 --- a/src/rtcp/rtcp_packet.h +++ b/src/rtcp/rtcp_packet.h @@ -19,7 +19,8 @@ class RtcpPacket { // not contain all data in this RtcpPacket; if a packet cannot fit in // max_length bytes, it will be fragmented and multiple calls to this // callback will be made. - using PacketReadyCallback = std::function packet)>; + using PacketReadyCallback = + std::function; virtual ~RtcpPacket() = default; @@ -32,7 +33,7 @@ class RtcpPacket { // Creates packet in the given buffer at the given position. // Calls PacketReadyCallback::OnPacketReady if remaining buffer is too small // and assume buffer can be reused after OnPacketReady returns. - virtual bool Create(std::vector& packet, size_t max_length, + virtual bool Create(uint8_t* packet, size_t* position, size_t max_length, PacketReadyCallback callback) const = 0; protected: @@ -42,14 +43,14 @@ class RtcpPacket { static void CreateHeader(size_t count_or_format, uint8_t packet_type, size_t block_length, // Payload size in 32bit words. - std::vector& buffer); + uint8_t* buffer, size_t* pos); static void CreateHeader(size_t count_or_format, uint8_t packet_type, size_t block_length, // Payload size in 32bit words. bool padding, // True if there are padding bytes. - std::vector& buffer); + uint8_t* buffer, size_t* pos); - bool OnBufferFull(std::vector& packet, + bool OnBufferFull(uint8_t* packet, size_t* index, PacketReadyCallback callback) const; // Size of the rtcp packet as written in header. size_t HeaderLength() const; diff --git a/src/rtcp/transport_feedback.cpp b/src/rtcp/transport_feedback.cpp index 6ad0e24..75ed318 100644 --- a/src/rtcp/transport_feedback.cpp +++ b/src/rtcp/transport_feedback.cpp @@ -1,5 +1,6 @@ #include "transport_feedback.h" +#include "byte_io.h" #include "log.h" #include "sequence_number_compare.h" @@ -135,62 +136,63 @@ void TransportFeedback::Clear() { } // Serialize packet. -bool TransportFeedback::Create(std::vector& packet, size_t max_length, +bool TransportFeedback::Create(uint8_t* packet, size_t* position, + size_t max_length, PacketReadyCallback callback) const { if (pkt_stat_cnt_ == 0) return false; - size_t position = packet.size(); - while (position + BlockLength() > max_length) { - if (!OnBufferFull(packet, callback)) return false; + while (*position + BlockLength() > max_length) { + if (!OnBufferFull(packet, position, callback)) return false; } - const size_t position_end = position + BlockLength(); + const size_t position_end = *position + BlockLength(); const size_t padding_length = PaddingLength(); bool has_padding = padding_length > 0; CreateHeader(kFeedbackMessageType, kPacketType, HeaderLength(), has_padding, - packet); - CreateCommonFeedback(packet + position); - position += kCommonFeedbackLength; + packet, position); + CreateCommonFeedback(packet + *position); + *position += kCommonFeedbackLength; - ByteWriter::WriteBigEndian(&packet[position], base_seq_no_); - position += 2; + ByteWriter::WriteBigEndian(&packet[*position], base_seq_no_); + *position += 2; - ByteWriter::WriteBigEndian(&packet[position], pkt_stat_cnt_); - position += 2; + ByteWriter::WriteBigEndian(&packet[*position], pkt_stat_cnt_); + *position += 2; - ByteWriter::WriteBigEndian(&packet[position], base_time_ticks_); - position += 3; + ByteWriter::WriteBigEndian(&packet[*position], ref_time_); + *position += 3; - packet[(position)++] = feedback_seq_; + packet[(*position)++] = feedback_pkt_cnt_; for (uint16_t chunk : encoded_chunks_) { - ByteWriter::WriteBigEndian(&packet[position], chunk); - position += 2; + ByteWriter::WriteBigEndian(&packet[*position], chunk); + *position += 2; } if (!last_chunk_.Empty()) { uint16_t chunk = last_chunk_.EncodeLast(); - ByteWriter::WriteBigEndian(&packet[position], chunk); - position += 2; + ByteWriter::WriteBigEndian(&packet[*position], chunk); + *position += 2; } - if (include_timestamps_) { - for (const auto& received_packet : received_packets_) { - int16_t delta = received_packet.delta_ticks(); - if (delta >= 0 && delta <= 0xFF) { - packet[(position)++] = delta; - } else { - ByteWriter::WriteBigEndian(&packet[position], delta); - position += 2; - } + for (const auto& received_packet : received_packets_) { + int16_t delta = received_packet.delta_ticks(); + if (delta >= 0 && delta <= 0xFF) { + packet[(*position)++] = delta; + } else { + ByteWriter::WriteBigEndian(&packet[*position], delta); + *position += 2; } } if (padding_length > 0) { for (size_t i = 0; i < padding_length - 1; ++i) { - packet[(position)++] = 0; + packet[(*position)++] = 0; } - packet[(position)++] = padding_length; + packet[(*position)++] = padding_length; + } + + if (*position != position_end) { + LOG_FATAL("padding_length is too small"); } - RTC_DCHECK_EQ(position, position_end); return true; } @@ -213,17 +215,36 @@ void TransportFeedback::SetFeedbackPacketCount(uint8_t feedback_packet_count) { int64_t TransportFeedback::BaseTime() const { // Add an extra kTimeWrapPeriod to allow add received packets arrived earlier // than the first added packet (and thus allow to record negative deltas) - // even when base_time_ticks_ == 0. - return 0 + kTimeWrapPeriod + int64_t{base_time_ticks_} * kBaseTimeTick; + // even when ref_time_ == 0. + return 0 + kTimeWrapPeriod + int64_t{ref_time_} * kBaseTimeTick; } int64_t TransportFeedback::GetBaseDelta(int64_t prev_timestamp) const { int64_t delta = BaseTime() - prev_timestamp; // Compensate for wrap around. - if (std::abs(delta - kTimeWrapPeriod) < std::abs(delta) { + if (std::abs(delta - kTimeWrapPeriod) < std::abs(delta)) { delta -= kTimeWrapPeriod; // Wrap backwards. } else if (std::abs(delta + kTimeWrapPeriod) < std::abs(delta)) { delta += kTimeWrapPeriod; // Wrap forwards. } return delta; +} + +size_t TransportFeedback::BlockLength() const { + // Round size_bytes_ up to multiple of 32bits. + return (size_bytes_ + 3) & (~static_cast(3)); +} + +size_t TransportFeedback::PaddingLength() const { + return BlockLength() - size_bytes_; +} + +void TransportFeedback::ParseCommonFeedback(const uint8_t* payload) { + SetSenderSsrc(ByteReader::ReadBigEndian(&payload[0])); + SetMediaSsrc(ByteReader::ReadBigEndian(&payload[4])); +} + +void TransportFeedback::CreateCommonFeedback(uint8_t* payload) const { + ByteWriter::WriteBigEndian(&payload[0], sender_ssrc()); + ByteWriter::WriteBigEndian(&payload[4], media_ssrc()); } \ No newline at end of file diff --git a/src/rtcp/transport_feedback.h b/src/rtcp/transport_feedback.h index 7ffef8c..d938ff0 100644 --- a/src/rtcp/transport_feedback.h +++ b/src/rtcp/transport_feedback.h @@ -131,16 +131,26 @@ class TransportFeedback : public RtcpPacket { TransportFeedback(); ~TransportFeedback(); + public: + static constexpr uint8_t kPacketType = 205; + public: bool AddReceivedPacket(uint16_t sequence_number, int64_t timestamp); bool AddMissingPackets(size_t num_missing_packets); bool AddDeltaSize(DeltaSize delta_size); void Clear(); - bool Create(std::vector& packet, size_t max_length, + bool Create(uint8_t* packet, size_t* position, size_t max_length, PacketReadyCallback callback) const override; public: + void SetMediaSsrc(uint32_t ssrc) { media_ssrc_ = ssrc; } + uint32_t media_ssrc() const { return media_ssrc_; } + void ParseCommonFeedback(const uint8_t* payload); + void CreateCommonFeedback(uint8_t* payload) const; + + public: + static constexpr size_t kCommonFeedbackLength = 8; void SetBaseSequenceNumber(uint16_t base_sequence_number); void SetPacketStatusCount(uint16_t packet_status_count); void SetReferenceTime(uint32_t reference_time); @@ -148,6 +158,11 @@ class TransportFeedback : public RtcpPacket { int64_t BaseTime() const; int64_t GetBaseDelta(int64_t prev_timestamp) const; + size_t BlockLength() const override; + size_t PaddingLength() const; + + private: + uint32_t media_ssrc_ = 0; private: uint16_t base_seq_no_;