1 // Copyright 2018 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/webrtc_event_log_manager_remote.h"
6 
7 #include <algorithm>
8 #include <iterator>
9 #include <utility>
10 
11 #include "base/bind.h"
12 #include "base/command_line.h"
13 #include "base/files/file.h"
14 #include "base/files/file_enumerator.h"
15 #include "base/files/file_path.h"
16 #include "base/files/file_util.h"
17 #include "base/logging.h"
18 #include "base/strings/string_number_conversions.h"
19 #include "base/strings/string_util.h"
20 #include "chrome/common/chrome_switches.h"
21 #include "content/public/browser/browser_task_traits.h"
22 #include "content/public/browser/browser_thread.h"
23 
24 namespace webrtc_event_logging {
25 
26 // TODO(crbug.com/775415): Change max back to (1u << 29) after resolving the
27 // issue where we read the entire file into memory.
28 const size_t kMaxRemoteLogFileSizeBytes = 50000000u;
29 
30 const int kDefaultOutputPeriodMs = 5000;
31 const int kMaxOutputPeriodMs = 60000;
32 
33 namespace {
34 const base::TimeDelta kDefaultProactivePruningDelta =
35     base::TimeDelta::FromMinutes(5);
36 
37 const base::TimeDelta kDefaultWebRtcRemoteEventLogUploadDelay =
38     base::TimeDelta::FromSeconds(30);
39 
40 // Because history files are rarely used, their existence is not kept in memory.
41 // That means that pruning them involves inspecting data on disk. This is not
42 // terribly cheap (up to kMaxWebRtcEventLogHistoryFiles files per profile), and
43 // should therefore be done somewhat infrequently.
44 const base::TimeDelta kProactiveHistoryFilesPruneDelta =
45     base::TimeDelta::FromMinutes(30);
46 
GetProactivePendingLogsPruneDelta()47 base::TimeDelta GetProactivePendingLogsPruneDelta() {
48   if (base::CommandLine::ForCurrentProcess()->HasSwitch(
49           ::switches::kWebRtcRemoteEventLogProactivePruningDelta)) {
50     const std::string delta_seconds_str =
51         base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
52             ::switches::kWebRtcRemoteEventLogProactivePruningDelta);
53     int64_t seconds;
54     if (base::StringToInt64(delta_seconds_str, &seconds) && seconds >= 0) {
55       return base::TimeDelta::FromSeconds(seconds);
56     } else {
57       LOG(WARNING) << "Proactive pruning delta could not be parsed.";
58     }
59   }
60 
61   return kDefaultProactivePruningDelta;
62 }
63 
GetUploadDelay()64 base::TimeDelta GetUploadDelay() {
65   if (base::CommandLine::ForCurrentProcess()->HasSwitch(
66           ::switches::kWebRtcRemoteEventLogUploadDelayMs)) {
67     const std::string delta_seconds_str =
68         base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
69             ::switches::kWebRtcRemoteEventLogUploadDelayMs);
70     int64_t ms;
71     if (base::StringToInt64(delta_seconds_str, &ms) && ms >= 0) {
72       return base::TimeDelta::FromMilliseconds(ms);
73     } else {
74       LOG(WARNING) << "Upload delay could not be parsed; using default delay.";
75     }
76   }
77 
78   return kDefaultWebRtcRemoteEventLogUploadDelay;
79 }
80 
TimePointInRange(const base::Time & time_point,const base::Time & range_begin,const base::Time & range_end)81 bool TimePointInRange(const base::Time& time_point,
82                       const base::Time& range_begin,
83                       const base::Time& range_end) {
84   DCHECK(!time_point.is_null());
85   DCHECK(range_begin.is_null() || range_end.is_null() ||
86          range_begin <= range_end);
87   return (range_begin.is_null() || range_begin <= time_point) &&
88          (range_end.is_null() || time_point < range_end);
89 }
90 
91 // Do not attempt to upload when there is no active connection.
92 // Do not attempt to upload if the connection is known to be a mobile one.
93 // Note #1: A device may have multiple connections, so this is not bullet-proof.
94 // Note #2: Does not attempt to recognize mobile hotspots.
UploadSupportedUsingConnectionType(network::mojom::ConnectionType connection)95 bool UploadSupportedUsingConnectionType(
96     network::mojom::ConnectionType connection) {
97   return connection != network::mojom::ConnectionType::CONNECTION_NONE &&
98          connection != network::mojom::ConnectionType::CONNECTION_2G &&
99          connection != network::mojom::ConnectionType::CONNECTION_3G &&
100          connection != network::mojom::ConnectionType::CONNECTION_4G;
101 }
102 
103 // Produce a history file for a given file.
CreateHistoryFile(const base::FilePath & log_file_path,const base::Time & capture_time)104 void CreateHistoryFile(const base::FilePath& log_file_path,
105                        const base::Time& capture_time) {
106   std::unique_ptr<WebRtcEventLogHistoryFileWriter> writer =
107       WebRtcEventLogHistoryFileWriter::Create(
108           GetWebRtcEventLogHistoryFilePath(log_file_path));
109   if (!writer) {
110     LOG(ERROR) << "Could not create history file.";
111     return;
112   }
113 
114   if (!writer->WriteCaptureTime(capture_time)) {
115     LOG(ERROR) << "Could not write capture time to history file.";
116     writer->Delete();
117     return;
118   }
119 }
120 
121 // The following is a list of entry types used to transmit information
122 // from GetHistory() to the caller (normally - the UI).
123 // Each entry is of type UploadList::UploadInfo. Depending on the entry
124 // type, the fields in the UploadInfo have different values:
125 // 1+2. Currently-being-captured or pending -> State::Pending && !upload_time.
126 //   3. Currently-being-uploaded -> State::Pending && upload_time.
127 //   4. Pruned before being uploaded -> State::NotUploaded && !upload_time.
128 //   5. Unsuccessful upload attempt -> State::NotUploaded && upload_time.
129 //   6. Successfully uploaded -> State::Uploaded.
130 //
131 // As for the meaning of the local_id field, its semantics change according to
132 // the above entry type.
133 // * For cases 1-3 above, it is the filename, since the log is still on disk.
134 // * For cases 5-6 above, it is the local log ID that the now-deleted file used
135 // * to have.
136 namespace history {
CreateActivelyCapturedLogEntry(const base::FilePath & path,const base::Time & capture_time)137 UploadList::UploadInfo CreateActivelyCapturedLogEntry(
138     const base::FilePath& path,
139     const base::Time& capture_time) {
140   using State = UploadList::UploadInfo::State;
141   const std::string filename = path.BaseName().MaybeAsASCII();
142   DCHECK(!filename.empty());
143   return UploadList::UploadInfo(std::string(), base::Time(), filename,
144                                 capture_time, State::Pending);
145 }
146 
CreatePendingLogEntry(const WebRtcLogFileInfo & log_info)147 UploadList::UploadInfo CreatePendingLogEntry(
148     const WebRtcLogFileInfo& log_info) {
149   using State = UploadList::UploadInfo::State;
150   const std::string filename = log_info.path.BaseName().MaybeAsASCII();
151   DCHECK(!filename.empty());
152   return UploadList::UploadInfo(std::string(), base::Time(), filename,
153                                 log_info.last_modified, State::Pending);
154 }
155 
CreateActivelyUploadedLogEntry(const WebRtcLogFileInfo & log_info,const base::Time & upload_time)156 UploadList::UploadInfo CreateActivelyUploadedLogEntry(
157     const WebRtcLogFileInfo& log_info,
158     const base::Time& upload_time) {
159   using State = UploadList::UploadInfo::State;
160   const std::string filename = log_info.path.BaseName().MaybeAsASCII();
161   DCHECK(!filename.empty());
162   return UploadList::UploadInfo(std::string(), upload_time, filename,
163                                 log_info.last_modified, State::Pending);
164 }
165 
CreateEntryFromHistoryFileReader(const WebRtcEventLogHistoryFileReader & reader)166 UploadList::UploadInfo CreateEntryFromHistoryFileReader(
167     const WebRtcEventLogHistoryFileReader& reader) {
168   using State = UploadList::UploadInfo::State;
169   const auto state =
170       reader.UploadId().empty() ? State::NotUploaded : State::Uploaded;
171   return UploadList::UploadInfo(reader.UploadId(), reader.UploadTime(),
172                                 reader.LocalId(), reader.CaptureTime(), state);
173 }
174 }  // namespace history
175 }  // namespace
176 
177 const size_t kMaxActiveRemoteBoundWebRtcEventLogs = 3;
178 const size_t kMaxPendingRemoteBoundWebRtcEventLogs = 5;
179 static_assert(kMaxActiveRemoteBoundWebRtcEventLogs <=
180                   kMaxPendingRemoteBoundWebRtcEventLogs,
181               "This assumption affects unit test coverage.");
182 const size_t kMaxWebRtcEventLogHistoryFiles = 50;
183 
184 // Maximum time to keep remote-bound logs on disk.
185 const base::TimeDelta kRemoteBoundWebRtcEventLogsMaxRetention =
186     base::TimeDelta::FromDays(7);
187 
188 // Maximum time to keep history files on disk. These serve to display an upload
189 // on chrome://webrtc-logs/. It is persisted for longer than the log itself.
190 const base::TimeDelta kHistoryFileRetention = base::TimeDelta::FromDays(30);
191 
WebRtcRemoteEventLogManager(WebRtcRemoteEventLogsObserver * observer,scoped_refptr<base::SequencedTaskRunner> task_runner)192 WebRtcRemoteEventLogManager::WebRtcRemoteEventLogManager(
193     WebRtcRemoteEventLogsObserver* observer,
194     scoped_refptr<base::SequencedTaskRunner> task_runner)
195     : upload_suppression_disabled_(
196           base::CommandLine::ForCurrentProcess()->HasSwitch(
197               ::switches::kWebRtcRemoteEventLogUploadNoSuppression)),
198       upload_delay_(GetUploadDelay()),
199       proactive_pending_logs_prune_delta_(GetProactivePendingLogsPruneDelta()),
200       proactive_prune_scheduling_started_(false),
201       observer_(observer),
202       network_connection_tracker_(nullptr),
203       uploading_supported_for_connection_type_(false),
204       scheduled_upload_tasks_(0),
205       uploader_factory_(
206           std::make_unique<WebRtcEventLogUploaderImpl::Factory>(task_runner)),
207       task_runner_(task_runner),
208       weak_ptr_factory_(
209           std::make_unique<base::WeakPtrFactory<WebRtcRemoteEventLogManager>>(
210               this)) {
211   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
212   // Proactive pruning would not do anything at the moment; it will be started
213   // with the first enabled browser context. This will all have the benefit
214   // of doing so on |task_runner_| rather than the UI thread.
215 }
216 
~WebRtcRemoteEventLogManager()217 WebRtcRemoteEventLogManager::~WebRtcRemoteEventLogManager() {
218   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
219   // TODO(crbug.com/775415): Purge from disk files which were being uploaded
220   // while destruction took place, thereby avoiding endless attempts to upload
221   // the same file.
222 
223   if (weak_ptr_factory_) {
224     // Not a unit test; that would have gone through ShutDownForTesting().
225     const bool will_delete =
226         task_runner_->DeleteSoon(FROM_HERE, weak_ptr_factory_.release());
227     DCHECK(!will_delete)
228         << "Task runners must have been stopped by this stage of shutdown.";
229   }
230 
231   if (network_connection_tracker_) {
232     // * |network_connection_tracker_| might already have posted a task back
233     //   to us, but it will not run, because |task_runner_| has already been
234     //   stopped.
235     // * RemoveNetworkConnectionObserver() should generally be called on the
236     //   same thread as AddNetworkConnectionObserver(), but in this case it's
237     //   okay to remove on a separate thread, because this only happens during
238     //   Chrome shutdown, when no others tasks are running; there can be no
239     //   concurrently executing notification from the tracker.
240     network_connection_tracker_->RemoveNetworkConnectionObserver(this);
241   }
242 }
243 
SetNetworkConnectionTracker(network::NetworkConnectionTracker * network_connection_tracker)244 void WebRtcRemoteEventLogManager::SetNetworkConnectionTracker(
245     network::NetworkConnectionTracker* network_connection_tracker) {
246   DCHECK(task_runner_->RunsTasksInCurrentSequence());
247   DCHECK(network_connection_tracker);
248   DCHECK(!network_connection_tracker_);
249 
250   // |this| is only destroyed (on the UI thread) after |task_runner_| stops,
251   // so AddNetworkConnectionObserver() is safe.
252 
253   network_connection_tracker_ = network_connection_tracker;
254   network_connection_tracker_->AddNetworkConnectionObserver(this);
255 
256   auto callback =
257       base::BindOnce(&WebRtcRemoteEventLogManager::OnConnectionChanged,
258                      weak_ptr_factory_->GetWeakPtr());
259   network::mojom::ConnectionType connection_type;
260   const bool sync_answer = network_connection_tracker_->GetConnectionType(
261       &connection_type, std::move(callback));
262 
263   if (sync_answer) {
264     OnConnectionChanged(connection_type);
265   }
266 
267   // Because this happens while enabling the first browser context, there is no
268   // necessity to consider uploading yet.
269   DCHECK_EQ(enabled_browser_contexts_.size(), 0u);
270 }
271 
SetLogFileWriterFactory(std::unique_ptr<LogFileWriter::Factory> log_file_writer_factory)272 void WebRtcRemoteEventLogManager::SetLogFileWriterFactory(
273     std::unique_ptr<LogFileWriter::Factory> log_file_writer_factory) {
274   DCHECK(task_runner_->RunsTasksInCurrentSequence());
275   DCHECK(log_file_writer_factory);
276   DCHECK(!log_file_writer_factory_);
277   log_file_writer_factory_ = std::move(log_file_writer_factory);
278 }
279 
EnableForBrowserContext(BrowserContextId browser_context_id,const base::FilePath & browser_context_dir)280 void WebRtcRemoteEventLogManager::EnableForBrowserContext(
281     BrowserContextId browser_context_id,
282     const base::FilePath& browser_context_dir) {
283   DCHECK(task_runner_->RunsTasksInCurrentSequence());
284   DCHECK(network_connection_tracker_)
285       << "SetNetworkConnectionTracker not called.";
286   DCHECK(log_file_writer_factory_) << "SetLogFileWriterFactory() not called.";
287 
288   if (BrowserContextEnabled(browser_context_id)) {
289     return;
290   }
291 
292   const base::FilePath remote_bound_logs_dir =
293       GetRemoteBoundWebRtcEventLogsDir(browser_context_dir);
294   if (!MaybeCreateLogsDirectory(remote_bound_logs_dir)) {
295     LOG(WARNING)
296         << "WebRtcRemoteEventLogManager couldn't create logs directory.";
297     return;
298   }
299 
300   enabled_browser_contexts_.emplace(browser_context_id, remote_bound_logs_dir);
301 
302   LoadLogsDirectory(browser_context_id, remote_bound_logs_dir);
303 
304   if (!proactive_prune_scheduling_started_) {
305     proactive_prune_scheduling_started_ = true;
306 
307     if (!proactive_pending_logs_prune_delta_.is_zero()) {
308       RecurringlyPrunePendingLogs();
309     }
310 
311     RecurringlyPruneHistoryFiles();
312   }
313 }
314 
DisableForBrowserContext(BrowserContextId browser_context_id)315 void WebRtcRemoteEventLogManager::DisableForBrowserContext(
316     BrowserContextId browser_context_id) {
317   DCHECK(task_runner_->RunsTasksInCurrentSequence());
318 
319   if (!BrowserContextEnabled(browser_context_id)) {
320     return;  // Enabling may have failed due to lacking permissions.
321   }
322 
323   enabled_browser_contexts_.erase(browser_context_id);
324 
325 #if DCHECK_IS_ON()
326   // DisableForBrowserContext() is called in one of two cases:
327   // 1. If Chrome is shutting down. In that case, all the RPHs associated with
328   //    this BrowserContext must already have exited, which should have
329   //    implicitly stopped all active logs.
330   // 2. Remote-bound logging is no longer allowed for this BrowserContext.
331   //    In that case, some peer connections associated with this BrowserContext
332   //    might still be active, or become active at a later time, but all
333   //    logs must have already been stopped.
334   auto pred = [browser_context_id](decltype(active_logs_)::value_type& log) {
335     return log.first.browser_context_id == browser_context_id;
336   };
337   DCHECK(std::count_if(active_logs_.begin(), active_logs_.end(), pred) == 0u);
338 #endif
339 
340   // Pending logs for this BrowserContext are no longer eligible for upload.
341   for (auto it = pending_logs_.begin(); it != pending_logs_.end();) {
342     if (it->browser_context_id == browser_context_id) {
343       it = pending_logs_.erase(it);
344     } else {
345       ++it;
346     }
347   }
348 
349   // Active uploads of logs associated with this BrowserContext must be stopped.
350   MaybeCancelUpload(base::Time::Min(), base::Time::Max(), browser_context_id);
351 
352   // Active logs may have been removed, which could remove upload suppression,
353   // or pending logs which were about to be uploaded may have been removed,
354   // so uploading may no longer be possible.
355   ManageUploadSchedule();
356 }
357 
PeerConnectionAdded(const PeerConnectionKey & key)358 bool WebRtcRemoteEventLogManager::PeerConnectionAdded(
359     const PeerConnectionKey& key) {
360   DCHECK(task_runner_->RunsTasksInCurrentSequence());
361 
362   PrunePendingLogs();  // Infrequent event - good opportunity to prune.
363 
364   const auto result = active_peer_connections_.emplace(key, std::string());
365 
366   // An upload about to start might need to be suppressed.
367   ManageUploadSchedule();
368 
369   return result.second;
370 }
371 
PeerConnectionRemoved(const PeerConnectionKey & key)372 bool WebRtcRemoteEventLogManager::PeerConnectionRemoved(
373     const PeerConnectionKey& key) {
374   DCHECK(task_runner_->RunsTasksInCurrentSequence());
375 
376   PrunePendingLogs();  // Infrequent event - good opportunity to prune.
377 
378   const auto peer_connection = active_peer_connections_.find(key);
379   if (peer_connection == active_peer_connections_.end()) {
380     return false;
381   }
382 
383   MaybeStopRemoteLogging(key);
384 
385   active_peer_connections_.erase(peer_connection);
386 
387   ManageUploadSchedule();  // Suppression might have been removed.
388 
389   return true;
390 }
391 
PeerConnectionSessionIdSet(const PeerConnectionKey & key,const std::string & session_id)392 bool WebRtcRemoteEventLogManager::PeerConnectionSessionIdSet(
393     const PeerConnectionKey& key,
394     const std::string& session_id) {
395   DCHECK(task_runner_->RunsTasksInCurrentSequence());
396 
397   PrunePendingLogs();  // Infrequent event - good opportunity to prune.
398 
399   if (session_id.empty()) {
400     LOG(ERROR) << "Empty session ID.";
401     return false;
402   }
403 
404   auto peer_connection = active_peer_connections_.find(key);
405   if (peer_connection == active_peer_connections_.end()) {
406     return false;  // Unknown peer connection; already closed?
407   }
408 
409   if (peer_connection->second.empty()) {
410     peer_connection->second = session_id;
411   } else if (session_id != peer_connection->second) {
412     LOG(ERROR) << "Session ID already set to " << peer_connection->second
413                << ". Cannot change to " << session_id << ".";
414     return false;
415   }
416 
417   return true;
418 }
419 
StartRemoteLogging(int render_process_id,BrowserContextId browser_context_id,const std::string & session_id,const base::FilePath & browser_context_dir,size_t max_file_size_bytes,int output_period_ms,size_t web_app_id,std::string * log_id,std::string * error_message)420 bool WebRtcRemoteEventLogManager::StartRemoteLogging(
421     int render_process_id,
422     BrowserContextId browser_context_id,
423     const std::string& session_id,
424     const base::FilePath& browser_context_dir,
425     size_t max_file_size_bytes,
426     int output_period_ms,
427     size_t web_app_id,
428     std::string* log_id,
429     std::string* error_message) {
430   DCHECK(task_runner_->RunsTasksInCurrentSequence());
431   DCHECK(log_id);
432   DCHECK(log_id->empty());
433   DCHECK(error_message);
434   DCHECK(error_message->empty());
435 
436   if (output_period_ms < 0) {
437     output_period_ms = kDefaultOutputPeriodMs;
438   }
439 
440   if (!AreLogParametersValid(max_file_size_bytes, output_period_ms, web_app_id,
441                              error_message)) {
442     // |error_message| will have been set by AreLogParametersValid().
443     DCHECK(!error_message->empty()) << "AreLogParametersValid() reported an "
444                                        "error without an error message.";
445     UmaRecordWebRtcEventLoggingApi(WebRtcEventLoggingApiUma::kInvalidArguments);
446     return false;
447   }
448 
449   if (session_id.empty()) {
450     *error_message = kStartRemoteLoggingFailureUnknownOrInactivePeerConnection;
451     UmaRecordWebRtcEventLoggingApi(WebRtcEventLoggingApiUma::kIllegalSessionId);
452     return false;
453   }
454 
455   if (!BrowserContextEnabled(browser_context_id)) {
456     // Remote-bound event logging has either not yet been enabled for this
457     // BrowserContext, or has been recently disabled. This error should not
458     // really be reached, barring a timing issue.
459     *error_message = kStartRemoteLoggingFailureLoggingDisabledBrowserContext;
460     UmaRecordWebRtcEventLoggingApi(
461         WebRtcEventLoggingApiUma::kDisabledBrowserContext);
462     return false;
463   }
464 
465   PeerConnectionKey key;
466   if (!FindPeerConnection(render_process_id, session_id, &key)) {
467     *error_message = kStartRemoteLoggingFailureUnknownOrInactivePeerConnection;
468     UmaRecordWebRtcEventLoggingApi(
469         WebRtcEventLoggingApiUma::kUnknownOrInvalidPeerConnection);
470     return false;
471   }
472 
473   // May not restart active remote logs.
474   auto it = active_logs_.find(key);
475   if (it != active_logs_.end()) {
476     LOG(ERROR) << "Remote logging already underway for " << session_id << ".";
477     *error_message = kStartRemoteLoggingFailureAlreadyLogging;
478     UmaRecordWebRtcEventLoggingApi(WebRtcEventLoggingApiUma::kAlreadyLogging);
479     return false;
480   }
481 
482   // This is a good opportunity to prune the list of pending logs, potentially
483   // making room for this file.
484   PrunePendingLogs();
485 
486   if (!AdditionalActiveLogAllowed(key.browser_context_id)) {
487     *error_message = kStartRemoteLoggingFailureNoAdditionalActiveLogsAllowed;
488     UmaRecordWebRtcEventLoggingApi(
489         WebRtcEventLoggingApiUma::kNoAdditionalLogsAllowed);
490     return false;
491   }
492 
493   return StartWritingLog(key, browser_context_dir, max_file_size_bytes,
494                          output_period_ms, web_app_id, log_id, error_message);
495 }
496 
EventLogWrite(const PeerConnectionKey & key,const std::string & message)497 bool WebRtcRemoteEventLogManager::EventLogWrite(const PeerConnectionKey& key,
498                                                 const std::string& message) {
499   DCHECK(task_runner_->RunsTasksInCurrentSequence());
500 
501   auto it = active_logs_.find(key);
502   if (it == active_logs_.end()) {
503     return false;
504   }
505 
506   const bool write_successful = it->second->Write(message);
507   if (!write_successful || it->second->MaxSizeReached()) {
508     // Note: If the file is invalid, CloseLogFile() will discard it.
509     CloseLogFile(it, /*make_pending=*/true);
510     ManageUploadSchedule();
511   }
512 
513   return write_successful;
514 }
515 
ClearCacheForBrowserContext(BrowserContextId browser_context_id,const base::Time & delete_begin,const base::Time & delete_end)516 void WebRtcRemoteEventLogManager::ClearCacheForBrowserContext(
517     BrowserContextId browser_context_id,
518     const base::Time& delete_begin,
519     const base::Time& delete_end) {
520   DCHECK(task_runner_->RunsTasksInCurrentSequence());
521   // Rationale for the order:
522   // 1. Active logs cancelled. This has no side effects, and can be safely
523   //    done before anything else.
524   // 2. Pending logs removed, before they can be considered as the
525   //    next log to be uploaded. This may cause history files to be created.
526   // 3. Remove history files, including those that #2 might have created.
527   // 4. Cancel any active upload precisely at a time when nothing being cleared
528   //    by ClearCacheForBrowserContext() could accidentally replace it.
529   // 5. Explicitly consider uploading, now that things have changed.
530   MaybeCancelActiveLogs(delete_begin, delete_end, browser_context_id);
531   MaybeRemovePendingLogs(delete_begin, delete_end, browser_context_id,
532                          /*is_cache_clear=*/true);
533   MaybeRemoveHistoryFiles(delete_begin, delete_end, browser_context_id);
534   MaybeCancelUpload(delete_begin, delete_end, browser_context_id);
535   ManageUploadSchedule();
536 }
537 
GetHistory(BrowserContextId browser_context_id,base::OnceCallback<void (const std::vector<UploadList::UploadInfo> &)> reply)538 void WebRtcRemoteEventLogManager::GetHistory(
539     BrowserContextId browser_context_id,
540     base::OnceCallback<void(const std::vector<UploadList::UploadInfo>&)>
541         reply) {
542   DCHECK(task_runner_->RunsTasksInCurrentSequence());
543 
544   std::vector<UploadList::UploadInfo> history;
545 
546   if (!BrowserContextEnabled(browser_context_id)) {
547     // Either the browser context is unknown, or more likely, it's not
548     // enabled for remote logging.
549     content::GetUIThreadTaskRunner({})->PostTask(
550         FROM_HERE, base::BindOnce(std::move(reply), history));
551     return;
552   }
553 
554   PrunePendingLogs(browser_context_id);
555 
556   const base::Time now = base::Time::Now();
557 
558   std::set<WebRtcEventLogHistoryFileReader> history_files =
559       PruneAndLoadHistoryFilesForBrowserContext(
560           base::Time::Min(), now - kHistoryFileRetention, browser_context_id);
561   for (const auto& history_file : history_files) {
562     history.push_back(history::CreateEntryFromHistoryFileReader(history_file));
563   }
564 
565   for (const WebRtcLogFileInfo& log_info : pending_logs_) {
566     if (browser_context_id == log_info.browser_context_id) {
567       history.push_back(history::CreatePendingLogEntry(log_info));
568     }
569   }
570 
571   for (const auto& it : active_logs_) {
572     if (browser_context_id == it.first.browser_context_id) {
573       history.push_back(
574           history::CreateActivelyCapturedLogEntry(it.second->path(), now));
575     }
576   }
577 
578   if (uploader_) {
579     const WebRtcLogFileInfo log_info = uploader_->GetWebRtcLogFileInfo();
580     if (browser_context_id == log_info.browser_context_id) {
581       history.push_back(history::CreateActivelyUploadedLogEntry(log_info, now));
582     }
583   }
584 
585   // Sort according to capture time, for consistent orders regardless of
586   // future operations on the log files.
587   auto cmp = [](const UploadList::UploadInfo& lhs,
588                 const UploadList::UploadInfo& rhs) {
589     if (lhs.capture_time == rhs.capture_time) {
590       // Resolve ties arbitrarily, but consistently. (Local ID expected to be
591       // distinct for distinct items; if not, anything goes.)
592       return lhs.local_id < rhs.local_id;
593     }
594     return (lhs.capture_time < rhs.capture_time);
595   };
596   std::sort(history.begin(), history.end(), cmp);
597 
598   content::GetUIThreadTaskRunner({})->PostTask(
599       FROM_HERE, base::BindOnce(std::move(reply), history));
600 }
601 
RemovePendingLogsForNotEnabledBrowserContext(BrowserContextId browser_context_id,const base::FilePath & browser_context_dir)602 void WebRtcRemoteEventLogManager::RemovePendingLogsForNotEnabledBrowserContext(
603     BrowserContextId browser_context_id,
604     const base::FilePath& browser_context_dir) {
605   DCHECK(task_runner_->RunsTasksInCurrentSequence());
606   DCHECK(!BrowserContextEnabled(browser_context_id));
607   const base::FilePath remote_bound_logs_dir =
608       GetRemoteBoundWebRtcEventLogsDir(browser_context_dir);
609   if (!base::DeletePathRecursively(remote_bound_logs_dir)) {
610     LOG(ERROR) << "Failed to delete  `" << remote_bound_logs_dir << ".";
611   }
612 }
613 
RenderProcessHostExitedDestroyed(int render_process_id)614 void WebRtcRemoteEventLogManager::RenderProcessHostExitedDestroyed(
615     int render_process_id) {
616   DCHECK(task_runner_->RunsTasksInCurrentSequence());
617 
618   // Remove all of the peer connections associated with this render process.
619   // It's important to do this before closing the actual files, because closing
620   // files can trigger a new upload if no active peer connections are present.
621   auto pc_it = active_peer_connections_.begin();
622   while (pc_it != active_peer_connections_.end()) {
623     if (pc_it->first.render_process_id == render_process_id) {
624       pc_it = active_peer_connections_.erase(pc_it);
625     } else {
626       ++pc_it;
627     }
628   }
629 
630   // Close all of the files that were associated with peer connections which
631   // belonged to this render process.
632   auto log_it = active_logs_.begin();
633   while (log_it != active_logs_.end()) {
634     if (log_it->first.render_process_id == render_process_id) {
635       log_it = CloseLogFile(log_it, /*make_pending=*/true);
636     } else {
637       ++log_it;
638     }
639   }
640 
641   ManageUploadSchedule();
642 }
643 
OnConnectionChanged(network::mojom::ConnectionType type)644 void WebRtcRemoteEventLogManager::OnConnectionChanged(
645     network::mojom::ConnectionType type) {
646   DCHECK(task_runner_->RunsTasksInCurrentSequence());
647   // Even if switching from WiFi to Ethernet, or between to WiFi connections,
648   // reset the timer (if running) until an upload is permissible due to stable
649   // upload-supporting conditions.
650   time_when_upload_conditions_met_ = base::TimeTicks();
651 
652   uploading_supported_for_connection_type_ =
653       UploadSupportedUsingConnectionType(type);
654 
655   ManageUploadSchedule();
656 
657   // TODO(crbug.com/775415): Support pausing uploads when connection goes down,
658   // or switches to an unsupported connection type.
659 }
660 
SetWebRtcEventLogUploaderFactoryForTesting(std::unique_ptr<WebRtcEventLogUploader::Factory> uploader_factory)661 void WebRtcRemoteEventLogManager::SetWebRtcEventLogUploaderFactoryForTesting(
662     std::unique_ptr<WebRtcEventLogUploader::Factory> uploader_factory) {
663   DCHECK(task_runner_->RunsTasksInCurrentSequence());
664   DCHECK(uploader_factory);
665   uploader_factory_ = std::move(uploader_factory);
666 }
667 
UploadConditionsHoldForTesting(base::OnceCallback<void (bool)> callback)668 void WebRtcRemoteEventLogManager::UploadConditionsHoldForTesting(
669     base::OnceCallback<void(bool)> callback) {
670   DCHECK(task_runner_->RunsTasksInCurrentSequence());
671   content::GetUIThreadTaskRunner({})->PostTask(
672       FROM_HERE, base::BindOnce(std::move(callback), UploadConditionsHold()));
673 }
674 
ShutDownForTesting(base::OnceClosure reply)675 void WebRtcRemoteEventLogManager::ShutDownForTesting(base::OnceClosure reply) {
676   DCHECK(task_runner_->RunsTasksInCurrentSequence());
677   weak_ptr_factory_->InvalidateWeakPtrs();
678   weak_ptr_factory_.reset();
679   content::GetUIThreadTaskRunner({})->PostTask(
680       FROM_HERE, base::BindOnce(std::move(reply)));
681 }
682 
AreLogParametersValid(size_t max_file_size_bytes,int output_period_ms,size_t web_app_id,std::string * error_message) const683 bool WebRtcRemoteEventLogManager::AreLogParametersValid(
684     size_t max_file_size_bytes,
685     int output_period_ms,
686     size_t web_app_id,
687     std::string* error_message) const {
688   DCHECK(task_runner_->RunsTasksInCurrentSequence());
689 
690   if (max_file_size_bytes == kWebRtcEventLogManagerUnlimitedFileSize) {
691     LOG(WARNING) << "Unlimited file sizes not allowed for remote-bound logs.";
692     *error_message = kStartRemoteLoggingFailureUnlimitedSizeDisallowed;
693     return false;
694   }
695 
696   if (max_file_size_bytes < log_file_writer_factory_->MinFileSizeBytes()) {
697     LOG(WARNING) << "File size below minimum allowed.";
698     *error_message = kStartRemoteLoggingFailureMaxSizeTooSmall;
699     return false;
700   }
701 
702   if (max_file_size_bytes > kMaxRemoteLogFileSizeBytes) {
703     LOG(WARNING) << "File size exceeds maximum allowed.";
704     *error_message = kStartRemoteLoggingFailureMaxSizeTooLarge;
705     return false;
706   }
707 
708   if (output_period_ms > kMaxOutputPeriodMs) {
709     LOG(WARNING) << "Output period (ms) exceeds maximum allowed.";
710     *error_message = kStartRemoteLoggingFailureOutputPeriodMsTooLarge;
711     return false;
712   }
713 
714   if (web_app_id < kMinWebRtcEventLogWebAppId ||
715       web_app_id > kMaxWebRtcEventLogWebAppId) {
716     LOG(WARNING) << "Illegal web-app identifier.";
717     *error_message = kStartRemoteLoggingFailureIllegalWebAppId;
718     return false;
719   }
720 
721   return true;
722 }
723 
BrowserContextEnabled(BrowserContextId browser_context_id) const724 bool WebRtcRemoteEventLogManager::BrowserContextEnabled(
725     BrowserContextId browser_context_id) const {
726   DCHECK(task_runner_->RunsTasksInCurrentSequence());
727   const auto it = enabled_browser_contexts_.find(browser_context_id);
728   return it != enabled_browser_contexts_.cend();
729 }
730 
731 WebRtcRemoteEventLogManager::LogFilesMap::iterator
CloseLogFile(LogFilesMap::iterator it,bool make_pending)732 WebRtcRemoteEventLogManager::CloseLogFile(LogFilesMap::iterator it,
733                                           bool make_pending) {
734   DCHECK(task_runner_->RunsTasksInCurrentSequence());
735 
736   const PeerConnectionKey peer_connection = it->first;  // Copy, not reference.
737 
738   const bool valid_file = it->second->Close();
739   if (valid_file) {
740     if (make_pending) {
741       // The current time is a good enough approximation of the file's last
742       // modification time.
743       const base::Time last_modified = base::Time::Now();
744 
745       // The stopped log becomes a pending log.
746       const auto emplace_result =
747           pending_logs_.emplace(peer_connection.browser_context_id,
748                                 it->second->path(), last_modified);
749       DCHECK(emplace_result.second);  // No pre-existing entry.
750     } else {
751       const base::FilePath log_file_path = it->second->path();
752       if (!base::DeleteFile(log_file_path)) {
753         LOG(ERROR) << "Failed to delete " << log_file_path << ".";
754       }
755     }
756   } else {  // !valid_file
757     // Close() deleted the file.
758     UmaRecordWebRtcEventLoggingUpload(
759         WebRtcEventLoggingUploadUma::kLogFileWriteError);
760   }
761 
762   it = active_logs_.erase(it);
763 
764   if (observer_) {
765     observer_->OnRemoteLogStopped(peer_connection);
766   }
767 
768   return it;
769 }
770 
MaybeCreateLogsDirectory(const base::FilePath & remote_bound_logs_dir)771 bool WebRtcRemoteEventLogManager::MaybeCreateLogsDirectory(
772     const base::FilePath& remote_bound_logs_dir) {
773   DCHECK(task_runner_->RunsTasksInCurrentSequence());
774 
775   if (base::PathExists(remote_bound_logs_dir)) {
776     if (!base::DirectoryExists(remote_bound_logs_dir)) {
777       LOG(ERROR) << "Path for remote-bound logs is taken by a non-directory.";
778       return false;
779     }
780   } else if (!base::CreateDirectory(remote_bound_logs_dir)) {
781     LOG(ERROR) << "Failed to create the local directory for remote-bound logs.";
782     return false;
783   }
784 
785   // TODO(crbug.com/775415): Test for appropriate permissions.
786 
787   return true;
788 }
789 
LoadLogsDirectory(BrowserContextId browser_context_id,const base::FilePath & remote_bound_logs_dir)790 void WebRtcRemoteEventLogManager::LoadLogsDirectory(
791     BrowserContextId browser_context_id,
792     const base::FilePath& remote_bound_logs_dir) {
793   DCHECK(task_runner_->RunsTasksInCurrentSequence());
794 
795   const auto separator =
796       base::FilePath::StringType(1, base::FilePath::kExtensionSeparator);
797   const base::Time now = base::Time::Now();
798 
799   std::set<std::pair<base::FilePath, base::Time>> log_files_to_delete;
800   std::set<base::FilePath> history_files_to_delete;
801 
802   // Iterate over all of the files in the directory; find the ones that need
803   // to be deleted. Skip unknown files; they may belong to the OS.
804   base::FileEnumerator enumerator(remote_bound_logs_dir,
805                                   /*recursive=*/false,
806                                   base::FileEnumerator::FILES);
807   for (auto path = enumerator.Next(); !path.empty(); path = enumerator.Next()) {
808     const base::FileEnumerator::FileInfo info = enumerator.GetInfo();
809     const base::FilePath::StringType extension = info.GetName().Extension();
810     if (extension == separator + kWebRtcEventLogUncompressedExtension ||
811         extension == separator + kWebRtcEventLogGzippedExtension) {
812       const bool loaded = LoadPendingLogInfo(
813           browser_context_id, path, enumerator.GetInfo().GetLastModifiedTime());
814       if (!loaded) {
815         log_files_to_delete.insert(
816             std::make_pair(path, info.GetLastModifiedTime()));
817       }
818     } else if (extension == separator + kWebRtcEventLogHistoryExtension) {
819       auto reader = LoadHistoryFile(browser_context_id, path, base::Time::Min(),
820                                     now - kHistoryFileRetention);
821       if (!reader) {
822         history_files_to_delete.insert(path);
823       }
824     }
825   }
826 
827   // Remove expired logs.
828   for (const auto& file_to_delete : log_files_to_delete) {
829     // Produce history file, unless we're discarding this log file precisely
830     // because we see it has a history file associated.
831     const base::FilePath& log_file_path = file_to_delete.first;
832     if (!base::PathExists(GetWebRtcEventLogHistoryFilePath(log_file_path))) {
833       const base::Time capture_time = file_to_delete.second;
834       CreateHistoryFile(log_file_path, capture_time);
835     }
836 
837     // Remove the log file itself.
838     if (!base::DeleteFile(log_file_path)) {
839       LOG(ERROR) << "Failed to delete " << file_to_delete.first << ".";
840     }
841   }
842 
843   // Remove expired history files.
844   for (const base::FilePath& history_file_path : history_files_to_delete) {
845     if (!base::DeleteFile(history_file_path)) {
846       LOG(ERROR) << "Failed to delete " << history_file_path << ".";
847     }
848   }
849 
850   ManageUploadSchedule();
851 }
852 
LoadPendingLogInfo(BrowserContextId browser_context_id,const base::FilePath & path,base::Time last_modified)853 bool WebRtcRemoteEventLogManager::LoadPendingLogInfo(
854     BrowserContextId browser_context_id,
855     const base::FilePath& path,
856     base::Time last_modified) {
857   DCHECK(task_runner_->RunsTasksInCurrentSequence());
858 
859   if (!IsValidRemoteBoundLogFilePath(path)) {
860     return false;
861   }
862 
863   const base::FilePath history_path = GetWebRtcEventLogHistoryFilePath(path);
864   if (base::PathExists(history_path)) {
865     // Log file has associated history file, indicating an upload was started
866     // for it. We should delete the original log from disk.
867     UmaRecordWebRtcEventLoggingUpload(
868         WebRtcEventLoggingUploadUma::kIncompletePastUpload);
869     return false;
870   }
871 
872   const base::Time now = base::Time::Now();
873   if (last_modified + kRemoteBoundWebRtcEventLogsMaxRetention < now) {
874     UmaRecordWebRtcEventLoggingUpload(
875         WebRtcEventLoggingUploadUma::kExpiredLogFileAtChromeStart);
876     return false;
877   }
878 
879   auto it = pending_logs_.emplace(browser_context_id, path, last_modified);
880   DCHECK(it.second);  // No pre-existing entry.
881 
882   return true;
883 }
884 
885 std::unique_ptr<WebRtcEventLogHistoryFileReader>
LoadHistoryFile(BrowserContextId browser_context_id,const base::FilePath & path,const base::Time & prune_begin,const base::Time & prune_end)886 WebRtcRemoteEventLogManager::LoadHistoryFile(
887     BrowserContextId browser_context_id,
888     const base::FilePath& path,
889     const base::Time& prune_begin,
890     const base::Time& prune_end) {
891   DCHECK(task_runner_->RunsTasksInCurrentSequence());
892 
893   if (!IsValidRemoteBoundLogFilePath(path)) {
894     return nullptr;
895   }
896 
897   std::unique_ptr<WebRtcEventLogHistoryFileReader> reader =
898       WebRtcEventLogHistoryFileReader::Create(path);
899   if (!reader) {
900     return nullptr;
901   }
902 
903   const base::Time capture_time = reader->CaptureTime();
904   if (prune_begin <= capture_time && capture_time <= prune_end) {
905     return nullptr;
906   }
907 
908   const base::Time upload_time = reader->UploadTime();
909   if (!upload_time.is_null()) {
910     if (prune_begin <= upload_time && upload_time <= prune_end) {
911       return nullptr;
912     }
913   }
914 
915   return reader;
916 }
917 
918 std::set<WebRtcEventLogHistoryFileReader>
PruneAndLoadHistoryFilesForBrowserContext(const base::Time & prune_begin,const base::Time & prune_end,BrowserContextId browser_context_id)919 WebRtcRemoteEventLogManager::PruneAndLoadHistoryFilesForBrowserContext(
920     const base::Time& prune_begin,
921     const base::Time& prune_end,
922     BrowserContextId browser_context_id) {
923   DCHECK(task_runner_->RunsTasksInCurrentSequence());
924 
925   std::set<WebRtcEventLogHistoryFileReader> history_files;
926 
927   auto browser_contexts_it = enabled_browser_contexts_.find(browser_context_id);
928   if (browser_contexts_it == enabled_browser_contexts_.end()) {
929     return history_files;
930   }
931 
932   std::set<base::FilePath> files_to_delete;
933 
934   base::FileEnumerator enumerator(browser_contexts_it->second,
935                                   /*recursive=*/false,
936                                   base::FileEnumerator::FILES);
937 
938   for (auto path = enumerator.Next(); !path.empty(); path = enumerator.Next()) {
939     const base::FileEnumerator::FileInfo info = enumerator.GetInfo();
940     const base::FilePath::StringType extension = info.GetName().Extension();
941     const auto separator =
942         base::FilePath::StringType(1, base::FilePath::kExtensionSeparator);
943     if (extension != separator + kWebRtcEventLogHistoryExtension) {
944       continue;
945     }
946 
947     if (uploader_) {
948       const base::FilePath log_path = uploader_->GetWebRtcLogFileInfo().path;
949       const base::FilePath history_path =
950           GetWebRtcEventLogHistoryFilePath(log_path);
951       if (path == history_path) {
952         continue;
953       }
954     }
955 
956     auto reader =
957         LoadHistoryFile(browser_context_id, path, prune_begin, prune_end);
958     if (reader) {
959       history_files.insert(std::move(*reader));
960       reader.reset();  // |reader| in undetermined state after move().
961     } else {           // Defective or expired.
962       files_to_delete.insert(path);
963     }
964   }
965 
966   // |history_files| is sorted by log capture time in ascending order;
967   // remove the oldest entries until kMaxWebRtcEventLogHistoryFiles is obeyed.
968   size_t num_history_files = history_files.size();
969   for (auto it = history_files.begin();
970        num_history_files > kMaxWebRtcEventLogHistoryFiles;
971        --num_history_files) {
972     DCHECK(it != history_files.end());
973     files_to_delete.insert(it->path());
974     it = history_files.erase(it);
975   }
976 
977   for (const base::FilePath& path : files_to_delete) {
978     if (!base::DeleteFile(path)) {
979       LOG(ERROR) << "Failed to delete " << path << ".";
980     }
981   }
982 
983   return history_files;
984 }
985 
StartWritingLog(const PeerConnectionKey & key,const base::FilePath & browser_context_dir,size_t max_file_size_bytes,int output_period_ms,size_t web_app_id,std::string * log_id_out,std::string * error_message_out)986 bool WebRtcRemoteEventLogManager::StartWritingLog(
987     const PeerConnectionKey& key,
988     const base::FilePath& browser_context_dir,
989     size_t max_file_size_bytes,
990     int output_period_ms,
991     size_t web_app_id,
992     std::string* log_id_out,
993     std::string* error_message_out) {
994   DCHECK(task_runner_->RunsTasksInCurrentSequence());
995 
996   // The log is assigned a universally unique ID (with high probability).
997   const std::string log_id = CreateWebRtcEventLogId();
998 
999   // Use the log ID as part of the filename. In the highly unlikely event that
1000   // this filename is already taken, or that an earlier log with the same name
1001   // existed and left a history file behind, it will be treated the same way as
1002   // any other failure to start the log file.
1003   // TODO(crbug.com/775415): Add a unit test for above comment.
1004   const base::FilePath remote_logs_dir =
1005       GetRemoteBoundWebRtcEventLogsDir(browser_context_dir);
1006   const base::FilePath log_path =
1007       WebRtcEventLogPath(remote_logs_dir, log_id, web_app_id,
1008                          log_file_writer_factory_->Extension());
1009 
1010   if (base::PathExists(log_path)) {
1011     LOG(ERROR) << "Previously used ID selected.";
1012     *error_message_out = kStartRemoteLoggingFailureFilePathUsedLog;
1013     UmaRecordWebRtcEventLoggingApi(
1014         WebRtcEventLoggingApiUma::kLogPathNotAvailable);
1015     return false;
1016   }
1017 
1018   const base::FilePath history_file_path =
1019       GetWebRtcEventLogHistoryFilePath(log_path);
1020   if (base::PathExists(history_file_path)) {
1021     LOG(ERROR) << "Previously used ID selected.";
1022     *error_message_out = kStartRemoteLoggingFailureFilePathUsedHistory;
1023     UmaRecordWebRtcEventLoggingApi(
1024         WebRtcEventLoggingApiUma::kHistoryPathNotAvailable);
1025     return false;
1026   }
1027 
1028   // The log is now ACTIVE.
1029   DCHECK_NE(max_file_size_bytes, kWebRtcEventLogManagerUnlimitedFileSize);
1030   auto log_file =
1031       log_file_writer_factory_->Create(log_path, max_file_size_bytes);
1032   if (!log_file) {
1033     LOG(ERROR) << "Failed to initialize remote-bound WebRTC event log file.";
1034     *error_message_out = kStartRemoteLoggingFailureFileCreationError;
1035     UmaRecordWebRtcEventLoggingApi(
1036         WebRtcEventLoggingApiUma::kFileCreationError);
1037     return false;
1038   }
1039   const auto it = active_logs_.emplace(key, std::move(log_file));
1040   DCHECK(it.second);
1041 
1042   observer_->OnRemoteLogStarted(key, it.first->second->path(),
1043                                 output_period_ms);
1044 
1045   UmaRecordWebRtcEventLoggingApi(WebRtcEventLoggingApiUma::kSuccess);
1046 
1047   *log_id_out = log_id;
1048   return true;
1049 }
1050 
MaybeStopRemoteLogging(const PeerConnectionKey & key)1051 void WebRtcRemoteEventLogManager::MaybeStopRemoteLogging(
1052     const PeerConnectionKey& key) {
1053   DCHECK(task_runner_->RunsTasksInCurrentSequence());
1054 
1055   const auto it = active_logs_.find(key);
1056   if (it == active_logs_.end()) {
1057     return;
1058   }
1059 
1060   CloseLogFile(it, /*make_pending=*/true);
1061 
1062   ManageUploadSchedule();
1063 }
1064 
PrunePendingLogs(base::Optional<BrowserContextId> browser_context_id)1065 void WebRtcRemoteEventLogManager::PrunePendingLogs(
1066     base::Optional<BrowserContextId> browser_context_id) {
1067   DCHECK(task_runner_->RunsTasksInCurrentSequence());
1068   MaybeRemovePendingLogs(
1069       base::Time::Min(),
1070       base::Time::Now() - kRemoteBoundWebRtcEventLogsMaxRetention,
1071       browser_context_id, /*is_cache_clear=*/false);
1072 }
1073 
RecurringlyPrunePendingLogs()1074 void WebRtcRemoteEventLogManager::RecurringlyPrunePendingLogs() {
1075   DCHECK(task_runner_->RunsTasksInCurrentSequence());
1076   DCHECK(!proactive_pending_logs_prune_delta_.is_zero());
1077   DCHECK(proactive_prune_scheduling_started_);
1078 
1079   PrunePendingLogs();
1080 
1081   task_runner_->PostDelayedTask(
1082       FROM_HERE,
1083       base::BindOnce(&WebRtcRemoteEventLogManager::RecurringlyPrunePendingLogs,
1084                      weak_ptr_factory_->GetWeakPtr()),
1085       proactive_pending_logs_prune_delta_);
1086 }
1087 
PruneHistoryFiles()1088 void WebRtcRemoteEventLogManager::PruneHistoryFiles() {
1089   DCHECK(task_runner_->RunsTasksInCurrentSequence());
1090   for (auto it = enabled_browser_contexts_.begin();
1091        it != enabled_browser_contexts_.end(); ++it) {
1092     const BrowserContextId browser_context_id = it->first;
1093     MaybeRemoveHistoryFiles(base::Time::Min(),
1094                             base::Time::Now() - kHistoryFileRetention,
1095                             browser_context_id);
1096   }
1097 }
1098 
RecurringlyPruneHistoryFiles()1099 void WebRtcRemoteEventLogManager::RecurringlyPruneHistoryFiles() {
1100   DCHECK(task_runner_->RunsTasksInCurrentSequence());
1101   DCHECK(proactive_prune_scheduling_started_);
1102 
1103   PruneHistoryFiles();
1104 
1105   task_runner_->PostDelayedTask(
1106       FROM_HERE,
1107       base::BindOnce(&WebRtcRemoteEventLogManager::RecurringlyPruneHistoryFiles,
1108                      weak_ptr_factory_->GetWeakPtr()),
1109       kProactiveHistoryFilesPruneDelta);
1110 }
1111 
MaybeCancelActiveLogs(const base::Time & delete_begin,const base::Time & delete_end,BrowserContextId browser_context_id)1112 void WebRtcRemoteEventLogManager::MaybeCancelActiveLogs(
1113     const base::Time& delete_begin,
1114     const base::Time& delete_end,
1115     BrowserContextId browser_context_id) {
1116   DCHECK(task_runner_->RunsTasksInCurrentSequence());
1117   for (auto it = active_logs_.begin(); it != active_logs_.end();) {
1118     // Since the file is active, assume it's still being modified.
1119     if (MatchesFilter(it->first.browser_context_id, base::Time::Now(),
1120                       browser_context_id, delete_begin, delete_end)) {
1121       UmaRecordWebRtcEventLoggingUpload(
1122           WebRtcEventLoggingUploadUma::kActiveLogCancelledDueToCacheClear);
1123       it = CloseLogFile(it, /*make_pending=*/false);
1124     } else {
1125       ++it;
1126     }
1127   }
1128 }
1129 
MaybeRemovePendingLogs(const base::Time & delete_begin,const base::Time & delete_end,base::Optional<BrowserContextId> browser_context_id,bool is_cache_clear)1130 void WebRtcRemoteEventLogManager::MaybeRemovePendingLogs(
1131     const base::Time& delete_begin,
1132     const base::Time& delete_end,
1133     base::Optional<BrowserContextId> browser_context_id,
1134     bool is_cache_clear) {
1135   DCHECK(task_runner_->RunsTasksInCurrentSequence());
1136 
1137   for (auto it = pending_logs_.begin(); it != pending_logs_.end();) {
1138     if (MatchesFilter(it->browser_context_id, it->last_modified,
1139                       browser_context_id, delete_begin, delete_end)) {
1140       UmaRecordWebRtcEventLoggingUpload(
1141           is_cache_clear
1142               ? WebRtcEventLoggingUploadUma::kPendingLogDeletedDueToCacheClear
1143               : WebRtcEventLoggingUploadUma::kExpiredLogFileDuringSession);
1144 
1145       if (!base::DeleteFile(it->path)) {
1146         LOG(ERROR) << "Failed to delete " << it->path << ".";
1147       }
1148 
1149       // Produce a history file (they have longer retention) to replace the log.
1150       if (is_cache_clear) {  // Will be immediately deleted otherwise.
1151         CreateHistoryFile(it->path, it->last_modified);
1152       }
1153 
1154       it = pending_logs_.erase(it);
1155     } else {
1156       ++it;
1157     }
1158   }
1159 
1160   // The last pending log might have been removed.
1161   if (!UploadConditionsHold()) {
1162     time_when_upload_conditions_met_ = base::TimeTicks();
1163   }
1164 }
1165 
MaybeRemoveHistoryFiles(const base::Time & delete_begin,const base::Time & delete_end,BrowserContextId browser_context_id)1166 void WebRtcRemoteEventLogManager::MaybeRemoveHistoryFiles(
1167     const base::Time& delete_begin,
1168     const base::Time& delete_end,
1169     BrowserContextId browser_context_id) {
1170   DCHECK(task_runner_->RunsTasksInCurrentSequence());
1171   PruneAndLoadHistoryFilesForBrowserContext(delete_begin, delete_end,
1172                                             browser_context_id);
1173   return;
1174 }
1175 
MaybeCancelUpload(const base::Time & delete_begin,const base::Time & delete_end,BrowserContextId browser_context_id)1176 void WebRtcRemoteEventLogManager::MaybeCancelUpload(
1177     const base::Time& delete_begin,
1178     const base::Time& delete_end,
1179     BrowserContextId browser_context_id) {
1180   DCHECK(task_runner_->RunsTasksInCurrentSequence());
1181 
1182   if (!uploader_) {
1183     return;
1184   }
1185 
1186   const WebRtcLogFileInfo& info = uploader_->GetWebRtcLogFileInfo();
1187   if (!MatchesFilter(info.browser_context_id, info.last_modified,
1188                      browser_context_id, delete_begin, delete_end)) {
1189     return;
1190   }
1191 
1192   // Cancel the upload. `uploader_` will be released when the callback,
1193   // `OnWebRtcEventLogUploadComplete`, is posted back.
1194   uploader_->Cancel();
1195 }
1196 
MatchesFilter(BrowserContextId log_browser_context_id,const base::Time & log_last_modification,base::Optional<BrowserContextId> filter_browser_context_id,const base::Time & filter_range_begin,const base::Time & filter_range_end) const1197 bool WebRtcRemoteEventLogManager::MatchesFilter(
1198     BrowserContextId log_browser_context_id,
1199     const base::Time& log_last_modification,
1200     base::Optional<BrowserContextId> filter_browser_context_id,
1201     const base::Time& filter_range_begin,
1202     const base::Time& filter_range_end) const {
1203   DCHECK(task_runner_->RunsTasksInCurrentSequence());
1204   if (filter_browser_context_id &&
1205       *filter_browser_context_id != log_browser_context_id) {
1206     return false;
1207   }
1208   return TimePointInRange(log_last_modification, filter_range_begin,
1209                           filter_range_end);
1210 }
1211 
AdditionalActiveLogAllowed(BrowserContextId browser_context_id) const1212 bool WebRtcRemoteEventLogManager::AdditionalActiveLogAllowed(
1213     BrowserContextId browser_context_id) const {
1214   DCHECK(task_runner_->RunsTasksInCurrentSequence());
1215 
1216   // Limit over concurrently active logs (across BrowserContext-s).
1217   if (active_logs_.size() >= kMaxActiveRemoteBoundWebRtcEventLogs) {
1218     return false;
1219   }
1220 
1221   // Limit over the number of pending logs (per BrowserContext). We count active
1222   // logs too, since they become pending logs once completed.
1223   const size_t active_count = std::count_if(
1224       active_logs_.begin(), active_logs_.end(),
1225       [browser_context_id](const decltype(active_logs_)::value_type& log) {
1226         return log.first.browser_context_id == browser_context_id;
1227       });
1228   const size_t pending_count = std::count_if(
1229       pending_logs_.begin(), pending_logs_.end(),
1230       [browser_context_id](const decltype(pending_logs_)::value_type& log) {
1231         return log.browser_context_id == browser_context_id;
1232       });
1233   return active_count + pending_count < kMaxPendingRemoteBoundWebRtcEventLogs;
1234 }
1235 
UploadSuppressed() const1236 bool WebRtcRemoteEventLogManager::UploadSuppressed() const {
1237   DCHECK(task_runner_->RunsTasksInCurrentSequence());
1238   return !upload_suppression_disabled_ && !active_peer_connections_.empty();
1239 }
1240 
UploadConditionsHold() const1241 bool WebRtcRemoteEventLogManager::UploadConditionsHold() const {
1242   DCHECK(task_runner_->RunsTasksInCurrentSequence());
1243   return !uploader_ && !pending_logs_.empty() && !UploadSuppressed() &&
1244          uploading_supported_for_connection_type_;
1245 }
1246 
ManageUploadSchedule()1247 void WebRtcRemoteEventLogManager::ManageUploadSchedule() {
1248   DCHECK(task_runner_->RunsTasksInCurrentSequence());
1249 
1250   PrunePendingLogs();  // Avoid uploading freshly expired files.
1251 
1252   if (!UploadConditionsHold()) {
1253     time_when_upload_conditions_met_ = base::TimeTicks();
1254     return;
1255   }
1256 
1257   if (!time_when_upload_conditions_met_.is_null()) {
1258     // Conditions have been holding for a while; MaybeStartUploading() has
1259     // already been scheduled when |time_when_upload_conditions_met_| was set.
1260     return;
1261   }
1262 
1263   ++scheduled_upload_tasks_;
1264 
1265   time_when_upload_conditions_met_ = base::TimeTicks::Now();
1266 
1267   task_runner_->PostDelayedTask(
1268       FROM_HERE,
1269       base::BindOnce(&WebRtcRemoteEventLogManager::MaybeStartUploading,
1270                      weak_ptr_factory_->GetWeakPtr()),
1271       upload_delay_);
1272 }
1273 
MaybeStartUploading()1274 void WebRtcRemoteEventLogManager::MaybeStartUploading() {
1275   DCHECK(task_runner_->RunsTasksInCurrentSequence());
1276   DCHECK_GT(scheduled_upload_tasks_, 0u);
1277 
1278   // Since MaybeStartUploading() was scheduled, conditions might have stopped
1279   // holding at some point. They may have even stopped and started several times
1280   // while the currently running task was scheduled, meaning several tasks could
1281   // be pending now, only the last of which should really end up uploading.
1282 
1283   if (time_when_upload_conditions_met_.is_null()) {
1284     // Conditions no longer hold; no way to know how many (now irrelevant) other
1285     // similar tasks are pending, if any.
1286   } else if (base::TimeTicks::Now() - time_when_upload_conditions_met_ <
1287              upload_delay_) {
1288     // Conditions have stopped holding, then started holding again; there has
1289     // to be a more recent task scheduled, that will take over later.
1290     DCHECK_GT(scheduled_upload_tasks_, 1u);
1291   } else {
1292     // It's up to the rest of the code to turn |scheduled_upload_tasks_| off
1293     // if the conditions have at some point stopped holding, or it wouldn't
1294     // know to turn it on when they resume.
1295     DCHECK(UploadConditionsHold());
1296 
1297     // When the upload we're about to start finishes, there will be another
1298     // delay of length |upload_delay_| before the next one starts.
1299     time_when_upload_conditions_met_ = base::TimeTicks();
1300 
1301     auto callback = base::BindOnce(
1302         &WebRtcRemoteEventLogManager::OnWebRtcEventLogUploadComplete,
1303         weak_ptr_factory_->GetWeakPtr());
1304 
1305     // The uploader takes ownership of the file; it's no longer considered to be
1306     // pending. (If the upload fails, the log will be deleted.)
1307     // TODO(crbug.com/775415): Add more refined retry behavior, so that we would
1308     // not delete the log permanently if the network is just down, on the one
1309     // hand, but also would not be uploading unlimited data on endless retries
1310     // on the other hand.
1311     // TODO(crbug.com/775415): Rename the file before uploading, so that we
1312     // would not retry the upload after restarting Chrome, if the upload is
1313     // interrupted.
1314     currently_uploaded_file_ = pending_logs_.begin()->path;
1315     uploader_ =
1316         uploader_factory_->Create(*pending_logs_.begin(), std::move(callback));
1317     pending_logs_.erase(pending_logs_.begin());
1318   }
1319 
1320   --scheduled_upload_tasks_;
1321 }
1322 
OnWebRtcEventLogUploadComplete(const base::FilePath & log_file,bool upload_successful)1323 void WebRtcRemoteEventLogManager::OnWebRtcEventLogUploadComplete(
1324     const base::FilePath& log_file,
1325     bool upload_successful) {
1326   DCHECK(task_runner_->RunsTasksInCurrentSequence());
1327   DCHECK(uploader_);
1328 
1329   // Make sure this callback refers to the currently uploaded file. This might
1330   // not be the case if the upload was cancelled right after succeeding, in
1331   // which case we'll get two callbacks, one reporting success and one failure.
1332   // It can also be that the uploader was cancelled more than once, e.g. if
1333   // the user cleared cache while PrefService were changing.
1334   if (!uploader_ ||
1335       uploader_->GetWebRtcLogFileInfo().path != currently_uploaded_file_) {
1336     return;
1337   }
1338 
1339   uploader_.reset();
1340   currently_uploaded_file_.clear();
1341 
1342   ManageUploadSchedule();
1343 }
1344 
FindPeerConnection(int render_process_id,const std::string & session_id,PeerConnectionKey * key) const1345 bool WebRtcRemoteEventLogManager::FindPeerConnection(
1346     int render_process_id,
1347     const std::string& session_id,
1348     PeerConnectionKey* key) const {
1349   DCHECK(task_runner_->RunsTasksInCurrentSequence());
1350   DCHECK(!session_id.empty());
1351 
1352   const auto it = FindNextPeerConnection(active_peer_connections_.cbegin(),
1353                                          render_process_id, session_id);
1354   if (it == active_peer_connections_.cend()) {
1355     return false;
1356   }
1357 
1358   // Make sure that the session ID is unique for the renderer process,
1359   // though not necessarily between renderer processes.
1360   // (The helper exists solely to allow this DCHECK.)
1361   DCHECK(FindNextPeerConnection(std::next(it), render_process_id, session_id) ==
1362          active_peer_connections_.cend());
1363 
1364   *key = it->first;
1365   return true;
1366 }
1367 
1368 WebRtcRemoteEventLogManager::PeerConnectionMap::const_iterator
FindNextPeerConnection(PeerConnectionMap::const_iterator begin,int render_process_id,const std::string & session_id) const1369 WebRtcRemoteEventLogManager::FindNextPeerConnection(
1370     PeerConnectionMap::const_iterator begin,
1371     int render_process_id,
1372     const std::string& session_id) const {
1373   DCHECK(task_runner_->RunsTasksInCurrentSequence());
1374   DCHECK(!session_id.empty());
1375   const auto end = active_peer_connections_.cend();
1376   for (auto it = begin; it != end; ++it) {
1377     if (it->first.render_process_id == render_process_id &&
1378         it->second == session_id) {
1379       return it;
1380     }
1381   }
1382   return end;
1383 }
1384 
1385 }  // namespace webrtc_event_logging
1386