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_history.h"
6
7 #include <limits>
8 #include <utility>
9 #include <vector>
10
11 #include "base/files/file_util.h"
12 #include "base/logging.h"
13 #include "base/memory/ptr_util.h"
14 #include "base/stl_util.h"
15 #include "base/strings/string_number_conversions.h"
16 #include "base/strings/string_split.h"
17 #include "chrome/browser/media/webrtc/webrtc_event_log_manager_common.h"
18
19 namespace webrtc_event_logging {
20
21 const size_t kWebRtcEventLogMaxUploadIdBytes = 100;
22
23 namespace {
24 // Compactness is not important for these few and small files; we therefore
25 // go with a human-readable format.
26 const char kCaptureTimeLinePrefix[] =
27 "Capture time (seconds since UNIX epoch): ";
28 const char kUploadTimeLinePrefix[] = "Upload time (seconds since UNIX epoch): ";
29 const char kUploadIdLinePrefix[] = "Upload ID: ";
30
31 // No need to use \r\n for Windows; better have a consistent file format
32 // between platforms.
33 const char kEOL[] = "\n";
34 static_assert(base::size(kEOL) == 1 + 1 /* +1 for the implicit \0. */,
35 "SplitString relies on this being a single character.");
36
37 // |time| must *not* be earlier than UNIX epoch start. If it is, the empty
38 // string is returned.
DeltaFromEpochSeconds(base::Time time)39 std::string DeltaFromEpochSeconds(base::Time time) {
40 if (time.is_null() || time.is_min() || time.is_max()) {
41 LOG(ERROR) << "Not a valid time (" << time << ").";
42 return std::string();
43 }
44
45 const base::Time epoch = base::Time::UnixEpoch();
46 if (time < epoch) {
47 LOG(WARNING) << "Time to go back to the future.";
48 return std::string();
49 }
50
51 return base::NumberToString((time - epoch).InSeconds());
52 }
53
54 // Helper for ParseTime; see its documentation for details.
StringToTime(const std::string & time)55 base::Time StringToTime(const std::string& time) {
56 int64_t seconds_from_epoch;
57 if (!base::StringToInt64(time, &seconds_from_epoch) ||
58 seconds_from_epoch < 0) {
59 LOG(WARNING) << "Error encountered while reading time.";
60 return base::Time();
61 }
62
63 return base::Time::UnixEpoch() +
64 base::TimeDelta::FromSeconds(seconds_from_epoch);
65 }
66
67 // Convert a history file's timestamp, which is the number of seconds since
68 // UNIX epoch, into a base::Time object.
69 // This function errors on timestamps from UNIX epoch or before it.
ParseTime(const std::string & line,const std::string & prefix,base::Time * out)70 bool ParseTime(const std::string& line,
71 const std::string& prefix,
72 base::Time* out) {
73 DCHECK(line.find(prefix) == 0);
74 DCHECK(out);
75
76 if (!out->is_null()) {
77 LOG(WARNING) << "Repeated line.";
78 return false;
79 }
80
81 const base::Time time = StringToTime(line.substr(prefix.length()));
82 if (time.is_null()) {
83 LOG(WARNING) << "Null time.";
84 return false;
85 }
86
87 *out = time;
88
89 return true;
90 }
91
ParseString(const std::string & line,const std::string & prefix,std::string * out)92 bool ParseString(const std::string& line,
93 const std::string& prefix,
94 std::string* out) {
95 DCHECK(line.find(prefix) == 0);
96 DCHECK(out);
97
98 if (!out->empty()) {
99 LOG(WARNING) << "Repeated line.";
100 return false;
101 }
102
103 *out = line.substr(prefix.length());
104
105 if (out->empty()) {
106 LOG(WARNING) << "Empty string.";
107 return false;
108 }
109
110 return true;
111 }
112 } // namespace
113
114 std::unique_ptr<WebRtcEventLogHistoryFileWriter>
Create(const base::FilePath & path)115 WebRtcEventLogHistoryFileWriter::Create(const base::FilePath& path) {
116 auto history_file_writer =
117 base::WrapUnique(new WebRtcEventLogHistoryFileWriter(path));
118 if (!history_file_writer->Init()) {
119 LOG(WARNING) << "Initialization of history file writer failed.";
120 return nullptr;
121 }
122 return history_file_writer;
123 }
124
WebRtcEventLogHistoryFileWriter(const base::FilePath & path)125 WebRtcEventLogHistoryFileWriter::WebRtcEventLogHistoryFileWriter(
126 const base::FilePath& path)
127 : path_(path), valid_(false) {}
128
Init()129 bool WebRtcEventLogHistoryFileWriter::Init() {
130 DCHECK(!valid_);
131
132 if (base::PathExists(path_)) {
133 if (!base::DeleteFile(path_, /*recursive=*/false)) {
134 LOG(ERROR) << "History file already exists, and could not be deleted.";
135 return false;
136 } else {
137 LOG(WARNING) << "History file already existed; deleted.";
138 }
139 }
140
141 // Attempt to create the file.
142 constexpr int file_flags = base::File::FLAG_CREATE | base::File::FLAG_WRITE |
143 base::File::FLAG_EXCLUSIVE_WRITE;
144 file_.Initialize(path_, file_flags);
145 if (!file_.IsValid() || !file_.created()) {
146 LOG(WARNING) << "Couldn't create history file.";
147 if (!base::DeleteFile(path_, /*recursive=*/false)) {
148 LOG(ERROR) << "Failed to delete " << path_ << ".";
149 }
150 return false;
151 }
152
153 valid_ = true;
154 return true;
155 }
156
WriteCaptureTime(base::Time capture_time)157 bool WebRtcEventLogHistoryFileWriter::WriteCaptureTime(
158 base::Time capture_time) {
159 DCHECK(valid_);
160
161 if (capture_time.is_null()) {
162 valid_ = false;
163 return false;
164 }
165
166 const std::string delta_seconds = DeltaFromEpochSeconds(capture_time);
167 if (delta_seconds.empty()) {
168 valid_ = false;
169 return false;
170 }
171
172 const bool written = Write(kCaptureTimeLinePrefix + delta_seconds + kEOL);
173 if (!written) {
174 // Error logged by Write().
175 valid_ = false;
176 return false;
177 }
178
179 return true;
180 }
181
WriteUploadTime(base::Time upload_time)182 bool WebRtcEventLogHistoryFileWriter::WriteUploadTime(base::Time upload_time) {
183 DCHECK(valid_);
184
185 if (upload_time.is_null()) {
186 valid_ = false;
187 return false;
188 }
189
190 const std::string delta_seconds = DeltaFromEpochSeconds(upload_time);
191 if (delta_seconds.empty()) {
192 valid_ = false;
193 return false;
194 }
195
196 const bool written = Write(kUploadTimeLinePrefix + delta_seconds + kEOL);
197 if (!written) {
198 valid_ = false;
199 return false;
200 }
201
202 return true;
203 }
204
WriteUploadId(const std::string & upload_id)205 bool WebRtcEventLogHistoryFileWriter::WriteUploadId(
206 const std::string& upload_id) {
207 DCHECK(valid_);
208 DCHECK(!upload_id.empty());
209 DCHECK_LE(upload_id.length(), kWebRtcEventLogMaxUploadIdBytes);
210
211 const bool written = Write(kUploadIdLinePrefix + upload_id + kEOL);
212 if (!written) {
213 valid_ = false;
214 return false;
215 }
216
217 return true;
218 }
219
Delete()220 void WebRtcEventLogHistoryFileWriter::Delete() {
221 if (!base::DeleteFile(path_, /*recursive=*/false)) {
222 LOG(ERROR) << "History file could not be deleted.";
223 }
224
225 valid_ = false; // Like was already false.
226 }
227
path() const228 base::FilePath WebRtcEventLogHistoryFileWriter::path() const {
229 DCHECK(valid_); // Can be performed on invalid objects, but likely shouldn't.
230 return path_;
231 }
232
Write(const std::string & str)233 bool WebRtcEventLogHistoryFileWriter::Write(const std::string& str) {
234 DCHECK(valid_);
235 DCHECK(!str.empty());
236 DCHECK_LE(str.length(), static_cast<size_t>(std::numeric_limits<int>::max()));
237
238 const int written = file_.WriteAtCurrentPos(str.c_str(), str.length());
239 if (written != static_cast<int>(str.length())) {
240 LOG(WARNING) << "Writing to history file failed.";
241 valid_ = false;
242 return false;
243 }
244
245 // Writes to the history file are infrequent, and happen on a |task_runner_|
246 // dedicated to event logs. We can therefore afford to Flush() after every
247 // write, giving us greater confidence that information would not get lost if,
248 // e.g., Chrome crashes.
249 file_.Flush();
250
251 return true;
252 }
253
254 std::unique_ptr<WebRtcEventLogHistoryFileReader>
Create(const base::FilePath & path)255 WebRtcEventLogHistoryFileReader::Create(const base::FilePath& path) {
256 auto history_file_reader =
257 base::WrapUnique(new WebRtcEventLogHistoryFileReader(path));
258 if (!history_file_reader->Init()) {
259 LOG(WARNING) << "Initialization of history file reader failed.";
260 return nullptr;
261 }
262 return history_file_reader;
263 }
264
WebRtcEventLogHistoryFileReader(const base::FilePath & path)265 WebRtcEventLogHistoryFileReader::WebRtcEventLogHistoryFileReader(
266 const base::FilePath& path)
267 : path_(path),
268 local_id_(ExtractRemoteBoundWebRtcEventLogLocalIdFromPath(path_)),
269 valid_(false) {}
270
WebRtcEventLogHistoryFileReader(WebRtcEventLogHistoryFileReader && other)271 WebRtcEventLogHistoryFileReader::WebRtcEventLogHistoryFileReader(
272 WebRtcEventLogHistoryFileReader&& other)
273 : path_(other.path_),
274 local_id_(other.local_id_),
275 capture_time_(other.capture_time_),
276 upload_time_(other.upload_time_),
277 upload_id_(other.upload_id_),
278 valid_(other.valid_) {
279 other.valid_ = false;
280 }
281
Init()282 bool WebRtcEventLogHistoryFileReader::Init() {
283 DCHECK(!valid_);
284
285 if (local_id_.empty()) {
286 LOG(WARNING) << "Unknown local ID.";
287 return false;
288 }
289
290 if (local_id_.length() > kWebRtcEventLogMaxUploadIdBytes) {
291 LOG(WARNING) << "Excessively long local ID.";
292 return false;
293 }
294
295 if (!base::PathExists(path_)) {
296 LOG(WARNING) << "File does not exist.";
297 return false;
298 }
299
300 constexpr int file_flags = base::File::FLAG_OPEN | base::File::FLAG_READ;
301 base::File file(path_, file_flags);
302 if (!file.IsValid()) {
303 LOG(WARNING) << "Couldn't read history file.";
304 if (!base::DeleteFile(path_, /*recursive=*/false)) {
305 LOG(ERROR) << "Failed to delete " << path_ << ".";
306 }
307 return false;
308 }
309
310 constexpr size_t kMaxHistoryFileSizeBytes = 1024;
311 static_assert(kWebRtcEventLogMaxUploadIdBytes < kMaxHistoryFileSizeBytes, "");
312
313 std::string file_contents;
314 file_contents.resize(kMaxHistoryFileSizeBytes);
315 const int read_bytes = file.Read(0, &file_contents[0], file_contents.size());
316 if (read_bytes < 0) {
317 LOG(WARNING) << "Couldn't read contents of history file.";
318 return false;
319 }
320 DCHECK_LE(static_cast<size_t>(read_bytes), file_contents.size());
321 file_contents.resize(static_cast<size_t>(read_bytes));
322 // Note: In excessively long files, the rest of the file will be ignored; the
323 // beginning of the file will encounter a parse error.
324
325 if (!Parse(file_contents)) {
326 LOG(WARNING) << "Parsing of history file failed.";
327 return false;
328 }
329
330 valid_ = true;
331 return true;
332 }
333
LocalId() const334 std::string WebRtcEventLogHistoryFileReader::LocalId() const {
335 DCHECK(valid_);
336 DCHECK(!local_id_.empty());
337 return local_id_;
338 }
339
CaptureTime() const340 base::Time WebRtcEventLogHistoryFileReader::CaptureTime() const {
341 DCHECK(valid_);
342 DCHECK(!capture_time_.is_null());
343 return capture_time_;
344 }
345
UploadTime() const346 base::Time WebRtcEventLogHistoryFileReader::UploadTime() const {
347 DCHECK(valid_);
348 return upload_time_; // May be null (which indicates "unset").
349 }
350
UploadId() const351 std::string WebRtcEventLogHistoryFileReader::UploadId() const {
352 DCHECK(valid_);
353 return upload_id_;
354 }
355
path() const356 base::FilePath WebRtcEventLogHistoryFileReader::path() const {
357 DCHECK(valid_);
358 return path_;
359 }
360
operator <(const WebRtcEventLogHistoryFileReader & other) const361 bool WebRtcEventLogHistoryFileReader::operator<(
362 const WebRtcEventLogHistoryFileReader& other) const {
363 DCHECK(valid_);
364 DCHECK(!capture_time_.is_null());
365 DCHECK(other.valid_);
366 DCHECK(!other.capture_time_.is_null());
367 if (capture_time_ == other.capture_time_) {
368 // Resolve ties arbitrarily, but consistently (Local IDs are unique).
369 return LocalId() < other.LocalId();
370 }
371 return (capture_time_ < other.capture_time_);
372 }
373
Parse(const std::string & file_contents)374 bool WebRtcEventLogHistoryFileReader::Parse(const std::string& file_contents) {
375 DCHECK(!valid_);
376 DCHECK(capture_time_.is_null());
377 DCHECK(upload_time_.is_null());
378 DCHECK(upload_id_.empty());
379
380 const std::vector<std::string> lines =
381 base::SplitString(file_contents, kEOL, base::TRIM_WHITESPACE,
382 base::SplitResult::SPLIT_WANT_NONEMPTY);
383
384 for (const std::string& line : lines) {
385 if (line.find(kCaptureTimeLinePrefix) == 0) {
386 if (!ParseTime(line, kCaptureTimeLinePrefix, &capture_time_)) {
387 return false;
388 }
389 } else if (line.find(kUploadTimeLinePrefix) == 0) {
390 if (!ParseTime(line, kUploadTimeLinePrefix, &upload_time_)) {
391 return false;
392 }
393 } else if (line.find(kUploadIdLinePrefix) == 0) {
394 if (!ParseString(line, kUploadIdLinePrefix, &upload_id_)) {
395 return false;
396 }
397 } else {
398 LOG(WARNING) << "Unrecognized line in history file.";
399 return false;
400 }
401 }
402
403 if (capture_time_.is_null()) {
404 LOG(WARNING) << "Incomplete history file; capture time unknown.";
405 return false;
406 }
407
408 if (!upload_id_.empty() && upload_time_.is_null()) {
409 LOG(WARNING) << "Incomplete history file; upload time known, "
410 << "but ID unknown.";
411 return false;
412 }
413
414 if (!upload_time_.is_null() && upload_time_ < capture_time_) {
415 LOG(WARNING) << "Defective history file; claims to have been uploaded "
416 << "before being captured.";
417 return false;
418 }
419
420 return true;
421 }
422
423 } // namespace webrtc_event_logging
424