1 // Copyright 2014 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/browser/media/webrtc_rtp_dump_handler.h" 6 7 #include "base/file_util.h" 8 #include "base/logging.h" 9 #include "base/strings/string_number_conversions.h" 10 #include "base/time/time.h" 11 #include "chrome/browser/media/webrtc_rtp_dump_writer.h" 12 #include "content/public/browser/browser_thread.h" 13 14 using content::BrowserThread; 15 16 namespace { 17 18 static const size_t kMaxOngoingRtpDumpsAllowed = 5; 19 20 // The browser process wide total number of ongoing (i.e. started and not 21 // released) RTP dumps. Incoming and outgoing in one WebRtcDumpHandler are 22 // counted as one dump. 23 // Must be accessed on the browser IO thread. 24 static size_t g_ongoing_rtp_dumps = 0; 25 26 void FireGenericDoneCallback( 27 const WebRtcRtpDumpHandler::GenericDoneCallback& callback, 28 bool success, 29 const std::string& error_message) { 30 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 31 DCHECK(!callback.is_null()); 32 33 content::BrowserThread::PostTask( 34 content::BrowserThread::UI, 35 FROM_HERE, 36 base::Bind(callback, success, error_message)); 37 } 38 39 bool DumpTypeContainsIncoming(RtpDumpType type) { 40 return type == RTP_DUMP_INCOMING || type == RTP_DUMP_BOTH; 41 } 42 43 bool DumpTypeContainsOutgoing(RtpDumpType type) { 44 return type == RTP_DUMP_OUTGOING || type == RTP_DUMP_BOTH; 45 } 46 47 } // namespace 48 49 WebRtcRtpDumpHandler::WebRtcRtpDumpHandler(const base::FilePath& dump_dir) 50 : dump_dir_(dump_dir), 51 incoming_state_(STATE_NONE), 52 outgoing_state_(STATE_NONE), 53 weak_ptr_factory_(this) { 54 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 55 } 56 57 WebRtcRtpDumpHandler::~WebRtcRtpDumpHandler() { 58 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 59 60 // Reset dump writer first to stop writing. 61 if (dump_writer_) { 62 --g_ongoing_rtp_dumps; 63 dump_writer_.reset(); 64 } 65 66 if (incoming_state_ != STATE_NONE && !incoming_dump_path_.empty()) { 67 BrowserThread::PostTask( 68 BrowserThread::FILE, 69 FROM_HERE, 70 base::Bind( 71 base::IgnoreResult(&base::DeleteFile), incoming_dump_path_, false)); 72 } 73 74 if (outgoing_state_ != STATE_NONE && !outgoing_dump_path_.empty()) { 75 BrowserThread::PostTask( 76 BrowserThread::FILE, 77 FROM_HERE, 78 base::Bind( 79 base::IgnoreResult(&base::DeleteFile), outgoing_dump_path_, false)); 80 } 81 } 82 83 bool WebRtcRtpDumpHandler::StartDump(RtpDumpType type, 84 std::string* error_message) { 85 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 86 87 if (!dump_writer_ && g_ongoing_rtp_dumps >= kMaxOngoingRtpDumpsAllowed) { 88 *error_message = "Max RTP dump limit reached."; 89 DVLOG(2) << *error_message; 90 return false; 91 } 92 93 // Returns an error if any type of dump specified by the caller cannot be 94 // started. 95 if ((DumpTypeContainsIncoming(type) && incoming_state_ != STATE_NONE) || 96 (DumpTypeContainsOutgoing(type) && outgoing_state_ != STATE_NONE)) { 97 *error_message = 98 "RTP dump already started for type " + base::IntToString(type); 99 return false; 100 } 101 102 if (DumpTypeContainsIncoming(type)) 103 incoming_state_ = STATE_STARTED; 104 105 if (DumpTypeContainsOutgoing(type)) 106 outgoing_state_ = STATE_STARTED; 107 108 DVLOG(2) << "Start RTP dumping: type = " << type; 109 110 if (!dump_writer_) { 111 ++g_ongoing_rtp_dumps; 112 113 static const char kRecvDumpFilePrefix[] = "rtpdump_recv_"; 114 static const char kSendDumpFilePrefix[] = "rtpdump_send_"; 115 static const size_t kMaxDumpSize = 5 * 1024 * 1024; // 5MB 116 117 std::string dump_id = base::DoubleToString(base::Time::Now().ToDoubleT()); 118 incoming_dump_path_ = 119 dump_dir_.AppendASCII(std::string(kRecvDumpFilePrefix) + dump_id) 120 .AddExtension(FILE_PATH_LITERAL(".gz")); 121 122 outgoing_dump_path_ = 123 dump_dir_.AppendASCII(std::string(kSendDumpFilePrefix) + dump_id) 124 .AddExtension(FILE_PATH_LITERAL(".gz")); 125 126 // WebRtcRtpDumpWriter does not support changing the dump path after it's 127 // created. So we assign both incoming and outgoing dump path even if only 128 // one type of dumping has been started. 129 // For "Unretained(this)", see comments StopDump. 130 dump_writer_.reset(new WebRtcRtpDumpWriter( 131 incoming_dump_path_, 132 outgoing_dump_path_, 133 kMaxDumpSize, 134 base::Bind(&WebRtcRtpDumpHandler::OnMaxDumpSizeReached, 135 base::Unretained(this)))); 136 } 137 138 return true; 139 } 140 141 void WebRtcRtpDumpHandler::StopDump(RtpDumpType type, 142 const GenericDoneCallback& callback) { 143 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 144 145 // Returns an error if any type of dump specified by the caller cannot be 146 // stopped. 147 if ((DumpTypeContainsIncoming(type) && incoming_state_ != STATE_STARTED) || 148 (DumpTypeContainsOutgoing(type) && outgoing_state_ != STATE_STARTED)) { 149 if (!callback.is_null()) { 150 FireGenericDoneCallback( 151 callback, 152 false, 153 "RTP dump not started or already stopped for type " + 154 base::IntToString(type)); 155 } 156 return; 157 } 158 159 DVLOG(2) << "Stopping RTP dumping: type = " << type; 160 161 if (DumpTypeContainsIncoming(type)) 162 incoming_state_ = STATE_STOPPING; 163 164 if (DumpTypeContainsOutgoing(type)) 165 outgoing_state_ = STATE_STOPPING; 166 167 // Using "Unretained(this)" because the this object owns the writer and the 168 // writer is guaranteed to cancel the callback before it goes away. Same for 169 // the other posted tasks bound to the writer. 170 dump_writer_->EndDump( 171 type, 172 base::Bind(&WebRtcRtpDumpHandler::OnDumpEnded, 173 base::Unretained(this), 174 callback.is_null() 175 ? base::Closure() 176 : base::Bind(&FireGenericDoneCallback, callback, true, ""), 177 type)); 178 } 179 180 bool WebRtcRtpDumpHandler::ReadyToRelease() const { 181 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 182 183 return incoming_state_ != STATE_STARTED && 184 incoming_state_ != STATE_STOPPING && 185 outgoing_state_ != STATE_STARTED && outgoing_state_ != STATE_STOPPING; 186 } 187 188 WebRtcRtpDumpHandler::ReleasedDumps WebRtcRtpDumpHandler::ReleaseDumps() { 189 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 190 DCHECK(ReadyToRelease()); 191 192 base::FilePath incoming_dump, outgoing_dump; 193 194 if (incoming_state_ == STATE_STOPPED) { 195 DVLOG(2) << "Incoming RTP dumps released: " << incoming_dump_path_.value(); 196 197 incoming_state_ = STATE_NONE; 198 incoming_dump = incoming_dump_path_; 199 } 200 201 if (outgoing_state_ == STATE_STOPPED) { 202 DVLOG(2) << "Outgoing RTP dumps released: " << outgoing_dump_path_.value(); 203 204 outgoing_state_ = STATE_NONE; 205 outgoing_dump = outgoing_dump_path_; 206 } 207 return ReleasedDumps(incoming_dump, outgoing_dump); 208 } 209 210 void WebRtcRtpDumpHandler::OnRtpPacket(const uint8* packet_header, 211 size_t header_length, 212 size_t packet_length, 213 bool incoming) { 214 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 215 216 if ((incoming && incoming_state_ != STATE_STARTED) || 217 (!incoming && outgoing_state_ != STATE_STARTED)) { 218 return; 219 } 220 221 dump_writer_->WriteRtpPacket( 222 packet_header, header_length, packet_length, incoming); 223 } 224 225 void WebRtcRtpDumpHandler::StopOngoingDumps(const base::Closure& callback) { 226 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 227 DCHECK(!callback.is_null()); 228 229 // No ongoing dumps, return directly. 230 if ((incoming_state_ == STATE_NONE || incoming_state_ == STATE_STOPPED) && 231 (outgoing_state_ == STATE_NONE || outgoing_state_ == STATE_STOPPED)) { 232 callback.Run(); 233 return; 234 } 235 236 // If the FILE thread is working on stopping the dumps, wait for the FILE 237 // thread to return and check the states again. 238 if (incoming_state_ == STATE_STOPPING || outgoing_state_ == STATE_STOPPING) { 239 BrowserThread::PostTaskAndReply( 240 BrowserThread::FILE, 241 FROM_HERE, 242 base::Bind(&base::DoNothing), 243 base::Bind(&WebRtcRtpDumpHandler::StopOngoingDumps, 244 weak_ptr_factory_.GetWeakPtr(), 245 callback)); 246 return; 247 } 248 249 // Either incoming or outgoing dump must be ongoing. 250 RtpDumpType type = 251 (incoming_state_ == STATE_STARTED) 252 ? (outgoing_state_ == STATE_STARTED ? RTP_DUMP_BOTH 253 : RTP_DUMP_INCOMING) 254 : RTP_DUMP_OUTGOING; 255 256 if (incoming_state_ == STATE_STARTED) 257 incoming_state_ = STATE_STOPPING; 258 259 if (outgoing_state_ == STATE_STARTED) 260 outgoing_state_ = STATE_STOPPING; 261 262 DVLOG(2) << "Stopping ongoing dumps: type = " << type; 263 264 dump_writer_->EndDump(type, 265 base::Bind(&WebRtcRtpDumpHandler::OnDumpEnded, 266 base::Unretained(this), 267 callback, 268 type)); 269 } 270 271 void WebRtcRtpDumpHandler::SetDumpWriterForTesting( 272 scoped_ptr<WebRtcRtpDumpWriter> writer) { 273 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 274 275 dump_writer_ = writer.Pass(); 276 ++g_ongoing_rtp_dumps; 277 278 incoming_dump_path_ = dump_dir_.AppendASCII("recv"); 279 outgoing_dump_path_ = dump_dir_.AppendASCII("send"); 280 } 281 282 void WebRtcRtpDumpHandler::OnMaxDumpSizeReached() { 283 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 284 285 RtpDumpType type = 286 (incoming_state_ == STATE_STARTED) 287 ? (outgoing_state_ == STATE_STARTED ? RTP_DUMP_BOTH 288 : RTP_DUMP_INCOMING) 289 : RTP_DUMP_OUTGOING; 290 StopDump(type, GenericDoneCallback()); 291 } 292 293 void WebRtcRtpDumpHandler::OnDumpEnded(const base::Closure& callback, 294 RtpDumpType ended_type, 295 bool incoming_success, 296 bool outgoing_success) { 297 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 298 299 if (DumpTypeContainsIncoming(ended_type)) { 300 DCHECK_EQ(STATE_STOPPING, incoming_state_); 301 incoming_state_ = STATE_STOPPED; 302 303 if (!incoming_success) { 304 BrowserThread::PostTask(BrowserThread::FILE, 305 FROM_HERE, 306 base::Bind(base::IgnoreResult(&base::DeleteFile), 307 incoming_dump_path_, 308 false)); 309 310 DVLOG(2) << "Deleted invalid incoming dump " 311 << incoming_dump_path_.value(); 312 incoming_dump_path_.clear(); 313 } 314 } 315 316 if (DumpTypeContainsOutgoing(ended_type)) { 317 DCHECK_EQ(STATE_STOPPING, outgoing_state_); 318 outgoing_state_ = STATE_STOPPED; 319 320 if (!outgoing_success) { 321 BrowserThread::PostTask(BrowserThread::FILE, 322 FROM_HERE, 323 base::Bind(base::IgnoreResult(&base::DeleteFile), 324 outgoing_dump_path_, 325 false)); 326 327 DVLOG(2) << "Deleted invalid outgoing dump " 328 << outgoing_dump_path_.value(); 329 outgoing_dump_path_.clear(); 330 } 331 } 332 333 // Release the writer when it's no longer needed. 334 if (incoming_state_ != STATE_STOPPING && outgoing_state_ != STATE_STOPPING && 335 incoming_state_ != STATE_STARTED && outgoing_state_ != STATE_STARTED) { 336 dump_writer_.reset(); 337 --g_ongoing_rtp_dumps; 338 } 339 340 // This object might be deleted after running the callback. 341 if (!callback.is_null()) 342 callback.Run(); 343 } 344