Added accessor and Parse function.

Create function merged into one.

BUG=webrtc:5260

Review URL: https://codereview.webrtc.org/1439553003

Cr-Commit-Position: refs/heads/master@{#11581}
This commit is contained in:
danilchap
2016-02-11 08:19:00 -08:00
committed by Commit bot
parent ca8352541a
commit 9f35d55c58
3 changed files with 350 additions and 95 deletions

View File

@ -10,22 +10,14 @@
#include "webrtc/modules/rtp_rtcp/source/rtcp_packet/sdes.h"
#include "webrtc/base/checks.h"
#include "webrtc/base/logging.h"
#include "webrtc/modules/rtp_rtcp/source/byte_io.h"
using webrtc::RTCPUtility::PT_SDES;
using webrtc::RTCPUtility::RtcpCommonHeader;
namespace webrtc {
namespace rtcp {
namespace {
void AssignUWord8(uint8_t* buffer, size_t* offset, uint8_t value) {
buffer[(*offset)++] = value;
}
void AssignUWord32(uint8_t* buffer, size_t* offset, uint32_t value) {
ByteWriter<uint32_t>::WriteBigEndian(buffer + *offset, value);
*offset += 4;
}
// Source Description (SDES) (RFC 3550).
//
// 0 1 2 3
@ -51,64 +43,144 @@ void AssignUWord32(uint8_t* buffer, size_t* offset, uint32_t value) {
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | CNAME=1 | length | user and domain name ...
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
void CreateSdes(const std::vector<Sdes::Chunk>& chunks,
uint8_t* buffer,
size_t* pos) {
const uint8_t kSdesItemType = 1;
for (std::vector<Sdes::Chunk>::const_iterator it = chunks.begin();
it != chunks.end(); ++it) {
AssignUWord32(buffer, pos, (*it).ssrc);
AssignUWord8(buffer, pos, kSdesItemType);
AssignUWord8(buffer, pos, (*it).name.length());
memcpy(buffer + *pos, (*it).name.data(), (*it).name.length());
*pos += (*it).name.length();
memset(buffer + *pos, 0, (*it).null_octets);
*pos += (*it).null_octets;
}
namespace {
const uint8_t kTerminatorTag = 0;
const uint8_t kCnameTag = 1;
size_t ChunkSize(const Sdes::Chunk& chunk) {
// Chunk:
// SSRC/CSRC (4 bytes) | CNAME=1 (1 byte) | length (1 byte) | cname | padding.
size_t chunk_payload_size = 4 + 1 + 1 + chunk.cname.size();
size_t padding_size = 4 - (chunk_payload_size % 4); // Minimum 1.
return chunk_payload_size + padding_size;
}
} // namespace
Sdes::Sdes() : block_length_(RtcpPacket::kHeaderLength) {}
Sdes::~Sdes() {}
bool Sdes::Parse(const RtcpCommonHeader& header, const uint8_t* payload) {
RTC_CHECK(header.packet_type == kPacketType);
uint8_t number_of_chunks = header.count_or_format;
std::vector<Chunk> chunks; // Read chunk into temporary array, so that in
// case of an error original array would stay
// unchanged.
size_t block_length = kHeaderLength;
if (header.payload_size_bytes % 4 != 0) {
LOG(LS_WARNING) << "Invalid payload size " << header.payload_size_bytes
<< " bytes for a valid Sdes packet. Size should be"
" multiple of 4 bytes";
}
const uint8_t* const payload_end = payload + header.payload_size_bytes;
const uint8_t* looking_at = payload;
chunks.resize(number_of_chunks);
for (size_t i = 0; i < number_of_chunks;) {
// Each chunk consumes at least 8 bytes.
if (payload_end - looking_at < 8) {
LOG(LS_WARNING) << "Not enough space left for chunk #" << (i + 1);
return false;
}
chunks[i].ssrc = ByteReader<uint32_t>::ReadBigEndian(looking_at);
looking_at += sizeof(uint32_t);
bool cname_found = false;
uint8_t item_type;
while ((item_type = *(looking_at++)) != kTerminatorTag) {
if (looking_at >= payload_end) {
LOG(LS_WARNING) << "Unexpected end of packet while reading chunk #"
<< (i + 1) << ". Expected to find size of the text.";
return false;
}
uint8_t item_length = *(looking_at++);
const size_t kTerminatorSize = 1;
if (looking_at + item_length + kTerminatorSize > payload_end) {
LOG(LS_WARNING) << "Unexpected end of packet while reading chunk #"
<< (i + 1) << ". Expected to find text of size "
<< item_length;
return false;
}
if (item_type == kCnameTag) {
if (cname_found) {
LOG(LS_WARNING) << "Found extra CNAME for same ssrc in chunk #"
<< (i + 1);
return false;
}
cname_found = true;
chunks[i].cname.assign(reinterpret_cast<const char*>(looking_at),
item_length);
}
looking_at += item_length;
}
if (cname_found) {
// block_length calculates length of the packet that would be generated by
// Build/Create functions. Adjust it same way WithCName function does.
block_length += ChunkSize(chunks[i]);
++i;
} else {
// RFC states CNAME item is mandatory.
// But same time it allows chunk without items.
// So while parsing, ignore all chunks without cname,
// but do not fail the parse.
LOG(LS_WARNING) << "CNAME not found for ssrc " << chunks[i].ssrc;
--number_of_chunks;
chunks.resize(number_of_chunks);
}
// Adjust to 32bit boundary.
looking_at += (payload_end - looking_at) % 4;
}
chunks_ = std::move(chunks);
block_length_ = block_length;
return true;
}
bool Sdes::WithCName(uint32_t ssrc, const std::string& cname) {
RTC_DCHECK_LE(cname.length(), 0xffu);
if (chunks_.size() >= kMaxNumberOfChunks) {
LOG(LS_WARNING) << "Max SDES chunks reached.";
return false;
}
Chunk chunk;
chunk.ssrc = ssrc;
chunk.cname = cname;
chunks_.push_back(chunk);
block_length_ += ChunkSize(chunk);
return true;
}
bool Sdes::Create(uint8_t* packet,
size_t* index,
size_t max_length,
RtcpPacket::PacketReadyCallback* callback) const {
assert(!chunks_.empty());
while (*index + BlockLength() > max_length) {
if (!OnBufferFull(packet, index, callback))
return false;
}
CreateHeader(chunks_.size(), PT_SDES, HeaderLength(), packet, index);
CreateSdes(chunks_, packet, index);
return true;
}
const size_t index_end = *index + BlockLength();
CreateHeader(chunks_.size(), kPacketType, HeaderLength(), packet, index);
bool Sdes::WithCName(uint32_t ssrc, const std::string& cname) {
assert(cname.length() <= 0xff);
if (chunks_.size() >= kMaxNumberOfChunks) {
LOG(LS_WARNING) << "Max SDES chunks reached.";
return false;
for (const Sdes::Chunk& chunk : chunks_) {
ByteWriter<uint32_t>::WriteBigEndian(&packet[*index + 0], chunk.ssrc);
ByteWriter<uint8_t>::WriteBigEndian(&packet[*index + 4], kCnameTag);
ByteWriter<uint8_t>::WriteBigEndian(&packet[*index + 5],
chunk.cname.size());
memcpy(&packet[*index + 6], chunk.cname.data(), chunk.cname.size());
*index += (6 + chunk.cname.size());
// In each chunk, the list of items must be terminated by one or more null
// octets. The next chunk must start on a 32-bit boundary.
// CNAME (1 byte) | length (1 byte) | name | padding.
size_t padding_size = 4 - ((6 + chunk.cname.size()) % 4);
const int kPadding = 0;
memset(packet + *index, kPadding, padding_size);
*index += padding_size;
}
// In each chunk, the list of items must be terminated by one or more null
// octets. The next chunk must start on a 32-bit boundary.
// CNAME (1 byte) | length (1 byte) | name | padding.
int null_octets = 4 - ((2 + cname.length()) % 4);
Chunk chunk;
chunk.ssrc = ssrc;
chunk.name = cname;
chunk.null_octets = null_octets;
chunks_.push_back(chunk);
return true;
}
size_t Sdes::BlockLength() const {
// Header (4 bytes).
// Chunk:
// SSRC/CSRC (4 bytes) | CNAME (1 byte) | length (1 byte) | name | padding.
size_t length = kHeaderLength;
for (const Chunk& chunk : chunks_)
length += 6 + chunk.name.length() + chunk.null_octets;
assert(length % 4 == 0);
return length;
RTC_CHECK_EQ(*index, index_end);
return true;
}
} // namespace rtcp
} // namespace webrtc

View File

@ -6,7 +6,6 @@
* 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 WEBRTC_MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_SDES_H_
@ -24,17 +23,24 @@ namespace rtcp {
// Source Description (SDES) (RFC 3550).
class Sdes : public RtcpPacket {
public:
Sdes() : RtcpPacket() {}
struct Chunk {
uint32_t ssrc;
std::string cname;
};
static const uint8_t kPacketType = 202;
virtual ~Sdes() {}
Sdes();
~Sdes() override;
// Parse assumes header is already parsed and validated.
bool Parse(const RTCPUtility::RtcpCommonHeader& header,
const uint8_t* payload); // Size of the payload is in the header.
bool WithCName(uint32_t ssrc, const std::string& cname);
struct Chunk {
uint32_t ssrc;
std::string name;
int null_octets;
};
const std::vector<Chunk>& chunks() const { return chunks_; }
size_t BlockLength() const override { return block_length_; }
protected:
bool Create(uint8_t* packet,
@ -43,11 +49,10 @@ class Sdes : public RtcpPacket {
RtcpPacket::PacketReadyCallback* callback) const override;
private:
static const int kMaxNumberOfChunks = 0x1f;
size_t BlockLength() const;
static const size_t kMaxNumberOfChunks = 0x1f;
std::vector<Chunk> chunks_;
size_t block_length_;
RTC_DISALLOW_COPY_AND_ASSIGN(Sdes);
};

View File

@ -12,51 +12,77 @@
#include "testing/gtest/include/gtest/gtest.h"
#include "webrtc/modules/rtp_rtcp/source/byte_io.h"
#include "webrtc/test/rtcp_packet_parser.h"
using webrtc::rtcp::RawPacket;
using webrtc::rtcp::Sdes;
using webrtc::test::RtcpPacketParser;
using webrtc::RTCPUtility::RtcpCommonHeader;
using webrtc::RTCPUtility::RtcpParseCommonHeader;
namespace webrtc {
namespace {
const uint32_t kSenderSsrc = 0x12345678;
const uint8_t kPadding = 0;
const uint8_t kTerminatorTag = 0;
const uint8_t kCnameTag = 1;
const uint8_t kNameTag = 2;
const uint8_t kEmailTag = 3;
bool Parse(const uint8_t* buffer, size_t length, Sdes* sdes) {
RtcpCommonHeader header;
EXPECT_TRUE(RtcpParseCommonHeader(buffer, length, &header));
// Check that there is exactly one RTCP packet in the buffer.
EXPECT_EQ(length, header.BlockSize());
return sdes->Parse(header, buffer + RtcpCommonHeader::kHeaderSizeBytes);
}
} // namespace
TEST(RtcpPacketSdesTest, WithoutChunks) {
Sdes sdes;
rtc::scoped_ptr<RawPacket> packet = sdes.Build();
Sdes parsed;
EXPECT_TRUE(Parse(packet->Buffer(), packet->Length(), &parsed));
EXPECT_EQ(0u, parsed.chunks().size());
}
TEST(RtcpPacketSdesTest, WithOneChunk) {
Sdes sdes;
EXPECT_TRUE(sdes.WithCName(kSenderSsrc, "alice@host"));
const std::string kCname = "alice@host";
rtc::scoped_ptr<RawPacket> packet(sdes.Build());
RtcpPacketParser parser;
parser.Parse(packet->Buffer(), packet->Length());
EXPECT_EQ(1, parser.sdes()->num_packets());
EXPECT_EQ(1, parser.sdes_chunk()->num_packets());
EXPECT_EQ(kSenderSsrc, parser.sdes_chunk()->Ssrc());
EXPECT_EQ("alice@host", parser.sdes_chunk()->Cname());
Sdes sdes;
EXPECT_TRUE(sdes.WithCName(kSenderSsrc, kCname));
rtc::scoped_ptr<RawPacket> packet = sdes.Build();
Sdes sdes_parsed;
Parse(packet->Buffer(), packet->Length(), &sdes_parsed);
const Sdes& parsed = sdes_parsed; // Ensure accessors are const.
EXPECT_EQ(1u, parsed.chunks().size());
EXPECT_EQ(kSenderSsrc, parsed.chunks()[0].ssrc);
EXPECT_EQ(kCname, parsed.chunks()[0].cname);
}
TEST(RtcpPacketSdesTest, WithMultipleChunks) {
Sdes sdes;
EXPECT_TRUE(sdes.WithCName(kSenderSsrc, "a"));
EXPECT_TRUE(sdes.WithCName(kSenderSsrc + 0, "a"));
EXPECT_TRUE(sdes.WithCName(kSenderSsrc + 1, "ab"));
EXPECT_TRUE(sdes.WithCName(kSenderSsrc + 2, "abc"));
EXPECT_TRUE(sdes.WithCName(kSenderSsrc + 3, "abcd"));
EXPECT_TRUE(sdes.WithCName(kSenderSsrc + 4, "abcde"));
EXPECT_TRUE(sdes.WithCName(kSenderSsrc + 5, "abcdef"));
rtc::scoped_ptr<RawPacket> packet(sdes.Build());
RtcpPacketParser parser;
parser.Parse(packet->Buffer(), packet->Length());
EXPECT_EQ(1, parser.sdes()->num_packets());
EXPECT_EQ(6, parser.sdes_chunk()->num_packets());
EXPECT_EQ(kSenderSsrc + 5, parser.sdes_chunk()->Ssrc());
EXPECT_EQ("abcdef", parser.sdes_chunk()->Cname());
rtc::scoped_ptr<RawPacket> packet = sdes.Build();
Sdes parsed;
Parse(packet->Buffer(), packet->Length(), &parsed);
EXPECT_EQ(6u, parsed.chunks().size());
EXPECT_EQ(kSenderSsrc + 5, parsed.chunks()[5].ssrc);
EXPECT_EQ("abcdef", parsed.chunks()[5].cname);
}
TEST(RtcpPacketSdesTest, WithTooManyChunks) {
const size_t kMaxChunks = (1 << 5) - 1;
Sdes sdes;
const int kMaxChunks = (1 << 5) - 1;
for (int i = 0; i < kMaxChunks; ++i) {
for (size_t i = 0; i < kMaxChunks; ++i) {
uint32_t ssrc = kSenderSsrc + i;
std::ostringstream oss;
oss << "cname" << i;
@ -69,13 +95,165 @@ TEST(RtcpPacketSdesTest, CnameItemWithEmptyString) {
Sdes sdes;
EXPECT_TRUE(sdes.WithCName(kSenderSsrc, ""));
rtc::scoped_ptr<RawPacket> packet(sdes.Build());
RtcpPacketParser parser;
parser.Parse(packet->Buffer(), packet->Length());
EXPECT_EQ(1, parser.sdes()->num_packets());
EXPECT_EQ(1, parser.sdes_chunk()->num_packets());
EXPECT_EQ(kSenderSsrc, parser.sdes_chunk()->Ssrc());
EXPECT_EQ("", parser.sdes_chunk()->Cname());
rtc::scoped_ptr<RawPacket> packet = sdes.Build();
Sdes parsed;
Parse(packet->Buffer(), packet->Length(), &parsed);
EXPECT_EQ(1u, parsed.chunks().size());
EXPECT_EQ(kSenderSsrc, parsed.chunks()[0].ssrc);
EXPECT_EQ("", parsed.chunks()[0].cname);
}
TEST(RtcpPacketSdesTest, ParseSkipsNonCNameField) {
const char kName[] = "abc";
const std::string kCname = "de";
const uint8_t kValidPacket[] = {0x81, 202, 0x00, 0x04,
0x12, 0x34, 0x56, 0x78,
kNameTag, 3, kName[0], kName[1], kName[2],
kCnameTag, 2, kCname[0], kCname[1],
kTerminatorTag, kPadding, kPadding};
// Sanity checks packet was assembled correctly.
ASSERT_EQ(0u, sizeof(kValidPacket) % 4);
ASSERT_EQ(kValidPacket[3] + 1u, sizeof(kValidPacket) / 4);
Sdes parsed;
EXPECT_TRUE(Parse(kValidPacket, sizeof(kValidPacket), &parsed));
EXPECT_EQ(1u, parsed.chunks().size());
EXPECT_EQ(kSenderSsrc, parsed.chunks()[0].ssrc);
EXPECT_EQ(kCname, parsed.chunks()[0].cname);
}
TEST(RtcpPacketSdesTest, ParseSkipsChunksWithoutCName) {
const char kName[] = "ab";
const char kEmail[] = "de";
const std::string kCname = "def";
const uint8_t kPacket[] = {0x82, 202, 0x00, 0x07,
0x12, 0x34, 0x56, 0x78, // 1st chunk.
kNameTag, 3, kName[0], kName[1], kName[2],
kEmailTag, 2, kEmail[0], kEmail[1],
kTerminatorTag, kPadding, kPadding,
0x23, 0x45, 0x67, 0x89, // 2nd chunk.
kCnameTag, 3, kCname[0], kCname[1], kCname[2],
kTerminatorTag, kPadding, kPadding};
// Sanity checks packet was assembled correctly.
ASSERT_EQ(0u, sizeof(kPacket) % 4);
ASSERT_EQ(kPacket[3] + 1u, sizeof(kPacket) / 4);
Sdes parsed;
EXPECT_TRUE(Parse(kPacket, sizeof(kPacket), &parsed));
ASSERT_EQ(1u, parsed.chunks().size());
EXPECT_EQ(0x23456789u, parsed.chunks()[0].ssrc);
EXPECT_EQ(kCname, parsed.chunks()[0].cname);
}
TEST(RtcpPacketSdesTest, ParseFailsWithoutChunkItemTerminator) {
const char kName[] = "abc";
const char kCname[] = "d";
// No place for next chunk item.
const uint8_t kInvalidPacket[] = {0x81, 202, 0x00, 0x03,
0x12, 0x34, 0x56, 0x78,
kNameTag, 3, kName[0], kName[1], kName[2],
kCnameTag, 1, kCname[0]};
// Sanity checks packet was assembled correctly.
ASSERT_EQ(0u, sizeof(kInvalidPacket) % 4);
ASSERT_EQ(kInvalidPacket[3] + 1u, sizeof(kInvalidPacket) / 4);
Sdes parsed;
EXPECT_FALSE(Parse(kInvalidPacket, sizeof(kInvalidPacket), &parsed));
}
TEST(RtcpPacketSdesTest, ParseFailsWithDamagedChunkItem) {
const char kName[] = "ab";
const char kCname[] = "d";
// Next chunk item has non-terminator type, but not the size.
const uint8_t kInvalidPacket[] = {0x81, 202, 0x00, 0x03,
0x12, 0x34, 0x56, 0x78,
kNameTag, 2, kName[0], kName[1],
kCnameTag, 1, kCname[0],
kEmailTag};
// Sanity checks packet was assembled correctly.
ASSERT_EQ(0u, sizeof(kInvalidPacket) % 4);
ASSERT_EQ(kInvalidPacket[3] + 1u, sizeof(kInvalidPacket) / 4);
Sdes parsed;
EXPECT_FALSE(Parse(kInvalidPacket, sizeof(kInvalidPacket), &parsed));
}
TEST(RtcpPacketSdesTest, ParseFailsWithTooLongChunkItem) {
const char kName[] = "abc";
const char kCname[] = "d";
// Last chunk item has length that goes beyond the buffer end.
const uint8_t kInvalidPacket[] = {0x81, 202, 0x00, 0x03,
0x12, 0x34, 0x56, 0x78,
kNameTag, 3, kName[0], kName[1], kName[2],
kCnameTag, 2, kCname[0]};
// Sanity checks packet was assembled correctly.
ASSERT_EQ(0u, sizeof(kInvalidPacket) % 4);
ASSERT_EQ(kInvalidPacket[3] + 1u, sizeof(kInvalidPacket) / 4);
Sdes parsed;
EXPECT_FALSE(Parse(kInvalidPacket, sizeof(kInvalidPacket), &parsed));
}
TEST(RtcpPacketSdesTest, ParseFailsWithTwoCNames) {
const char kCname1[] = "a";
const char kCname2[] = "de";
const uint8_t kInvalidPacket[] = {0x81, 202, 0x00, 0x03,
0x12, 0x34, 0x56, 0x78,
kCnameTag, 1, kCname1[0],
kCnameTag, 2, kCname2[0], kCname2[1],
kTerminatorTag};
// Sanity checks packet was assembled correctly.
ASSERT_EQ(0u, sizeof(kInvalidPacket) % 4);
ASSERT_EQ(kInvalidPacket[3] + 1u, sizeof(kInvalidPacket) / 4);
Sdes parsed;
EXPECT_FALSE(Parse(kInvalidPacket, sizeof(kInvalidPacket), &parsed));
}
TEST(RtcpPacketSdesTest, ParseFailsWithTooLittleSpaceForNextChunk) {
const char kCname[] = "a";
const char kEmail[] = "de";
// Two chunks are promised in the header, but no place for the second chunk.
const uint8_t kInvalidPacket[] = {0x82, 202, 0x00, 0x04,
0x12, 0x34, 0x56, 0x78, // 1st chunk.
kCnameTag, 1, kCname[0],
kEmailTag, 2, kEmail[0], kEmail[1],
kTerminatorTag,
0x23, 0x45, 0x67, 0x89}; // 2nd chunk.
// Sanity checks packet was assembled correctly.
ASSERT_EQ(0u, sizeof(kInvalidPacket) % 4);
ASSERT_EQ(kInvalidPacket[3] + 1u, sizeof(kInvalidPacket) / 4);
Sdes parsed;
EXPECT_FALSE(Parse(kInvalidPacket, sizeof(kInvalidPacket), &parsed));
}
TEST(RtcpPacketSdesTest, ParsedSdesCanBeReusedForBuilding) {
Sdes source;
const std::string kAlice = "alice@host";
const std::string kBob = "bob@host";
source.WithCName(kSenderSsrc, kAlice);
rtc::scoped_ptr<RawPacket> packet1 = source.Build();
Sdes middle;
Parse(packet1->Buffer(), packet1->Length(), &middle);
EXPECT_EQ(source.BlockLength(), middle.BlockLength());
middle.WithCName(kSenderSsrc + 1, kBob);
rtc::scoped_ptr<RawPacket> packet2 = middle.Build();
Sdes destination;
Parse(packet2->Buffer(), packet2->Length(), &destination);
EXPECT_EQ(middle.BlockLength(), destination.BlockLength());
EXPECT_EQ(2u, destination.chunks().size());
EXPECT_EQ(kSenderSsrc, destination.chunks()[0].ssrc);
EXPECT_EQ(kAlice, destination.chunks()[0].cname);
EXPECT_EQ(kSenderSsrc + 1, destination.chunks()[1].ssrc);
EXPECT_EQ(kBob, destination.chunks()[1].cname);
}
} // namespace webrtc