// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/big_endian.h" #include "base/bind.h" #include "base/files/file_util.h" #include "base/files/scoped_temp_dir.h" #include "base/macros.h" #include "base/memory/scoped_ptr.h" #include "base/run_loop.h" #include "chrome/browser/media/webrtc_rtp_dump_writer.h" #include "content/public/browser/browser_thread.h" #include "content/public/test/test_browser_thread_bundle.h" #include "content/public/test/test_utils.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/zlib/zlib.h" static const size_t kMinimumRtpHeaderLength = 12; static void CreateFakeRtpPacketHeader(size_t csrc_count, size_t extension_header_count, std::vector* packet_header) { packet_header->resize(kMinimumRtpHeaderLength + csrc_count * sizeof(uint32) + (extension_header_count + 1) * sizeof(uint32)); memset(&(*packet_header)[0], 0, packet_header->size()); // First byte format: vvpxcccc, where 'vv' is the version, 'p' is padding, 'x' // is the extension bit, 'cccc' is the CSRC count. (*packet_header)[0] = 0; (*packet_header)[0] |= (0x2 << 6); // version. // The extension bit. (*packet_header)[0] |= (extension_header_count > 0 ? (0x1 << 4) : 0); (*packet_header)[0] |= (csrc_count & 0xf); // Set extension length. size_t offset = kMinimumRtpHeaderLength + (csrc_count & 0xf) * sizeof(uint32) + sizeof(uint16); base::WriteBigEndian(reinterpret_cast(&(*packet_header)[offset]), static_cast(extension_header_count)); } class WebRtcRtpDumpWriterTest : public testing::Test { public: WebRtcRtpDumpWriterTest() : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP | content::TestBrowserThreadBundle::REAL_FILE_THREAD), temp_dir_(new base::ScopedTempDir()) {} virtual void SetUp() { ASSERT_TRUE(temp_dir_->CreateUniqueTempDir()); incoming_dump_path_ = temp_dir_->path().AppendASCII("rtpdump_recv"); outgoing_dump_path_ = temp_dir_->path().AppendASCII("rtpdump_send"); writer_.reset(new WebRtcRtpDumpWriter( incoming_dump_path_, outgoing_dump_path_, 4 * 1024 * 1024, base::Bind(&WebRtcRtpDumpWriterTest::OnMaxSizeReached, base::Unretained(this)))); } // Verifies that the dump contains records of |rtp_packet| repeated // |packet_count| times. void VerifyDumps(size_t incoming_packet_count, size_t outgoing_packet_count) { std::string incoming_dump; std::string outgoing_dump; if (incoming_packet_count) { EXPECT_TRUE(base::ReadFileToString(incoming_dump_path_, &incoming_dump)); EXPECT_TRUE(VerifyCompressedDump(&incoming_dump, incoming_packet_count)); } else { EXPECT_FALSE(base::PathExists(incoming_dump_path_)); } if (outgoing_packet_count) { EXPECT_TRUE(base::ReadFileToString(outgoing_dump_path_, &outgoing_dump)); EXPECT_TRUE(VerifyCompressedDump(&outgoing_dump, outgoing_packet_count)); } else { EXPECT_FALSE(base::PathExists(outgoing_dump_path_)); } } MOCK_METHOD2(OnEndDumpDone, void(bool, bool)); MOCK_METHOD0(OnMaxSizeReached, void(void)); protected: // Verifies the compressed dump file contains the expected number of packets. bool VerifyCompressedDump(std::string* dump, size_t expected_packet_count) { EXPECT_GT(dump->size(), 0U); std::vector decompressed_dump; EXPECT_TRUE(Decompress(dump, &decompressed_dump)); size_t actual_packet_count = 0; EXPECT_TRUE(ReadDecompressedDump(decompressed_dump, &actual_packet_count)); EXPECT_EQ(expected_packet_count, actual_packet_count); return true; } // Decompresses the |input| into |output|. bool Decompress(std::string* input, std::vector* output) { z_stream stream = {0}; int result = inflateInit2(&stream, 15 + 16); EXPECT_EQ(Z_OK, result); output->resize(input->size() * 100); stream.next_in = reinterpret_cast(const_cast(&(*input)[0])); stream.avail_in = input->size(); stream.next_out = &(*output)[0]; stream.avail_out = output->size(); result = inflate(&stream, Z_FINISH); DCHECK_EQ(Z_STREAM_END, result); result = inflateEnd(&stream); DCHECK_EQ(Z_OK, result); output->resize(output->size() - stream.avail_out); return true; } // Tries to read |dump| as a rtpplay dump file and returns the number of // packets found in the dump. bool ReadDecompressedDump(const std::vector& dump, size_t* packet_count) { static const char kFirstLine[] = "#!rtpplay1.0 0.0.0.0/0\n"; static const size_t kDumpFileHeaderSize = 4 * sizeof(uint32); *packet_count = 0; size_t dump_pos = 0; // Verifies the first line. EXPECT_EQ(memcmp(&dump[0], kFirstLine, arraysize(kFirstLine) - 1), 0); dump_pos += arraysize(kFirstLine) - 1; EXPECT_GT(dump.size(), dump_pos); // Skips the file header. dump_pos += kDumpFileHeaderSize; EXPECT_GT(dump.size(), dump_pos); // Reads each packet dump. while (dump_pos < dump.size()) { size_t packet_dump_length = 0; if (!VerifyPacketDump(&dump[dump_pos], dump.size() - dump_pos, &packet_dump_length)) { DVLOG(0) << "Failed to read the packet dump for packet " << *packet_count << ", dump_pos = " << dump_pos << ", dump_length = " << dump.size(); return false; } EXPECT_GE(dump.size(), dump_pos + packet_dump_length); dump_pos += packet_dump_length; (*packet_count)++; } return true; } // Tries to read one packet dump starting at |dump| and returns the size of // the packet dump. bool VerifyPacketDump(const uint8* dump, size_t dump_length, size_t* packet_dump_length) { static const size_t kDumpHeaderLength = 8; size_t dump_pos = 0; base::ReadBigEndian(reinterpret_cast(dump + dump_pos), reinterpret_cast(packet_dump_length)); if (*packet_dump_length < kDumpHeaderLength + kMinimumRtpHeaderLength) return false; EXPECT_GE(dump_length, *packet_dump_length); dump_pos += sizeof(uint16); uint16 rtp_packet_length = 0; base::ReadBigEndian(reinterpret_cast(dump + dump_pos), &rtp_packet_length); if (rtp_packet_length < kMinimumRtpHeaderLength) return false; dump_pos += sizeof(uint16); // Skips the elapsed time field. dump_pos += sizeof(uint32); return IsValidRtpHeader(dump + dump_pos, *packet_dump_length - kDumpHeaderLength); } // Returns true if |header| is a valid RTP header. bool IsValidRtpHeader(const uint8* header, size_t length) { if ((header[0] & 0xC0) != 0x80) return false; size_t cc_count = header[0] & 0x0F; size_t header_length_without_extn = kMinimumRtpHeaderLength + 4 * cc_count; if (length < header_length_without_extn) return false; uint16 extension_count = 0; base::ReadBigEndian( reinterpret_cast(header + header_length_without_extn + 2), &extension_count); if (length < (extension_count + 1) * 4 + header_length_without_extn) return false; return true; } content::TestBrowserThreadBundle thread_bundle_; scoped_ptr temp_dir_; base::FilePath incoming_dump_path_; base::FilePath outgoing_dump_path_; scoped_ptr writer_; }; TEST_F(WebRtcRtpDumpWriterTest, NoDumpFileIfNoPacketDumped) { // The scope is used to make sure the EXPECT_CALL is checked before exiting // the scope. { EXPECT_CALL(*this, OnEndDumpDone(false, false)); writer_->EndDump(RTP_DUMP_BOTH, base::Bind(&WebRtcRtpDumpWriterTest::OnEndDumpDone, base::Unretained(this))); content::RunAllPendingInMessageLoop(content::BrowserThread::FILE); base::RunLoop().RunUntilIdle(); content::RunAllPendingInMessageLoop(content::BrowserThread::FILE); base::RunLoop().RunUntilIdle(); } EXPECT_FALSE(base::PathExists(incoming_dump_path_)); EXPECT_FALSE(base::PathExists(outgoing_dump_path_)); } TEST_F(WebRtcRtpDumpWriterTest, WriteAndFlushSmallSizeDump) { std::vector packet_header; CreateFakeRtpPacketHeader(1, 2, &packet_header); writer_->WriteRtpPacket( &packet_header[0], packet_header.size(), 100, true); writer_->WriteRtpPacket( &packet_header[0], packet_header.size(), 100, false); // The scope is used to make sure the EXPECT_CALL is checked before exiting // the scope. { EXPECT_CALL(*this, OnEndDumpDone(true, true)); writer_->EndDump(RTP_DUMP_BOTH, base::Bind(&WebRtcRtpDumpWriterTest::OnEndDumpDone, base::Unretained(this))); content::RunAllPendingInMessageLoop(content::BrowserThread::FILE); base::RunLoop().RunUntilIdle(); content::RunAllPendingInMessageLoop(content::BrowserThread::FILE); base::RunLoop().RunUntilIdle(); } VerifyDumps(1, 1); } TEST_F(WebRtcRtpDumpWriterTest, WriteOverMaxLimit) { // Reset the writer with a small max size limit. writer_.reset(new WebRtcRtpDumpWriter( incoming_dump_path_, outgoing_dump_path_, 100, base::Bind(&WebRtcRtpDumpWriterTest::OnMaxSizeReached, base::Unretained(this)))); std::vector packet_header; CreateFakeRtpPacketHeader(3, 4, &packet_header); const size_t kPacketCount = 200; // The scope is used to make sure the EXPECT_CALL is checked before exiting // the scope. { EXPECT_CALL(*this, OnMaxSizeReached()).Times(testing::AtLeast(1)); // Write enough packets to overflow the in-memory buffer and max limit. for (size_t i = 0; i < kPacketCount; ++i) { writer_->WriteRtpPacket( &packet_header[0], packet_header.size(), 100, true); writer_->WriteRtpPacket( &packet_header[0], packet_header.size(), 100, false); } EXPECT_CALL(*this, OnEndDumpDone(true, true)); writer_->EndDump(RTP_DUMP_BOTH, base::Bind(&WebRtcRtpDumpWriterTest::OnEndDumpDone, base::Unretained(this))); content::RunAllPendingInMessageLoop(content::BrowserThread::FILE); base::RunLoop().RunUntilIdle(); content::RunAllPendingInMessageLoop(content::BrowserThread::FILE); base::RunLoop().RunUntilIdle(); } VerifyDumps(kPacketCount, kPacketCount); } TEST_F(WebRtcRtpDumpWriterTest, DestroyWriterBeforeEndDumpCallback) { EXPECT_CALL(*this, OnEndDumpDone(testing::_, testing::_)).Times(0); writer_->EndDump(RTP_DUMP_BOTH, base::Bind(&WebRtcRtpDumpWriterTest::OnEndDumpDone, base::Unretained(this))); writer_.reset(); content::RunAllPendingInMessageLoop(content::BrowserThread::FILE); base::RunLoop().RunUntilIdle(); content::RunAllPendingInMessageLoop(content::BrowserThread::FILE); base::RunLoop().RunUntilIdle(); } TEST_F(WebRtcRtpDumpWriterTest, EndDumpsSeparately) { std::vector packet_header; CreateFakeRtpPacketHeader(1, 2, &packet_header); writer_->WriteRtpPacket( &packet_header[0], packet_header.size(), 100, true); writer_->WriteRtpPacket( &packet_header[0], packet_header.size(), 100, true); writer_->WriteRtpPacket( &packet_header[0], packet_header.size(), 100, false); // The scope is used to make sure the EXPECT_CALL is checked before exiting // the scope. { EXPECT_CALL(*this, OnEndDumpDone(true, false)); EXPECT_CALL(*this, OnEndDumpDone(false, true)); writer_->EndDump(RTP_DUMP_INCOMING, base::Bind(&WebRtcRtpDumpWriterTest::OnEndDumpDone, base::Unretained(this))); writer_->EndDump(RTP_DUMP_OUTGOING, base::Bind(&WebRtcRtpDumpWriterTest::OnEndDumpDone, base::Unretained(this))); content::RunAllPendingInMessageLoop(content::BrowserThread::FILE); base::RunLoop().RunUntilIdle(); content::RunAllPendingInMessageLoop(content::BrowserThread::FILE); base::RunLoop().RunUntilIdle(); } VerifyDumps(2, 1); }