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_)) {
134 LOG(ERROR) << "History file already exists, and could not be deleted.";
135 return false;
136 }
137 LOG(WARNING) << "History file already existed; deleted.";
138 }
139
140 // Attempt to create the file.
141 constexpr int file_flags = base::File::FLAG_CREATE | base::File::FLAG_WRITE |
142 base::File::FLAG_EXCLUSIVE_WRITE;
143 file_.Initialize(path_, file_flags);
144 if (!file_.IsValid() || !file_.created()) {
145 LOG(WARNING) << "Couldn't create history file.";
146 if (!base::DeleteFile(path_)) {
147 LOG(ERROR) << "Failed to delete " << path_ << ".";
148 }
149 return false;
150 }
151
152 valid_ = true;
153 return true;
154 }
155
WriteCaptureTime(base::Time capture_time)156 bool WebRtcEventLogHistoryFileWriter::WriteCaptureTime(
157 base::Time capture_time) {
158 DCHECK(valid_);
159
160 if (capture_time.is_null()) {
161 valid_ = false;
162 return false;
163 }
164
165 const std::string delta_seconds = DeltaFromEpochSeconds(capture_time);
166 if (delta_seconds.empty()) {
167 valid_ = false;
168 return false;
169 }
170
171 const bool written = Write(kCaptureTimeLinePrefix + delta_seconds + kEOL);
172 if (!written) {
173 // Error logged by Write().
174 valid_ = false;
175 return false;
176 }
177
178 return true;
179 }
180
WriteUploadTime(base::Time upload_time)181 bool WebRtcEventLogHistoryFileWriter::WriteUploadTime(base::Time upload_time) {
182 DCHECK(valid_);
183
184 if (upload_time.is_null()) {
185 valid_ = false;
186 return false;
187 }
188
189 const std::string delta_seconds = DeltaFromEpochSeconds(upload_time);
190 if (delta_seconds.empty()) {
191 valid_ = false;
192 return false;
193 }
194
195 const bool written = Write(kUploadTimeLinePrefix + delta_seconds + kEOL);
196 if (!written) {
197 valid_ = false;
198 return false;
199 }
200
201 return true;
202 }
203
WriteUploadId(const std::string & upload_id)204 bool WebRtcEventLogHistoryFileWriter::WriteUploadId(
205 const std::string& upload_id) {
206 DCHECK(valid_);
207 DCHECK(!upload_id.empty());
208 DCHECK_LE(upload_id.length(), kWebRtcEventLogMaxUploadIdBytes);
209
210 const bool written = Write(kUploadIdLinePrefix + upload_id + kEOL);
211 if (!written) {
212 valid_ = false;
213 return false;
214 }
215
216 return true;
217 }
218
Delete()219 void WebRtcEventLogHistoryFileWriter::Delete() {
220 if (!base::DeleteFile(path_)) {
221 LOG(ERROR) << "History file could not be deleted.";
222 }
223
224 valid_ = false; // Like was already false.
225 }
226
path() const227 base::FilePath WebRtcEventLogHistoryFileWriter::path() const {
228 DCHECK(valid_); // Can be performed on invalid objects, but likely shouldn't.
229 return path_;
230 }
231
Write(const std::string & str)232 bool WebRtcEventLogHistoryFileWriter::Write(const std::string& str) {
233 DCHECK(valid_);
234 DCHECK(!str.empty());
235 DCHECK_LE(str.length(), static_cast<size_t>(std::numeric_limits<int>::max()));
236
237 const int written = file_.WriteAtCurrentPos(str.c_str(), str.length());
238 if (written != static_cast<int>(str.length())) {
239 LOG(WARNING) << "Writing to history file failed.";
240 valid_ = false;
241 return false;
242 }
243
244 // Writes to the history file are infrequent, and happen on a |task_runner_|
245 // dedicated to event logs. We can therefore afford to Flush() after every
246 // write, giving us greater confidence that information would not get lost if,
247 // e.g., Chrome crashes.
248 file_.Flush();
249
250 return true;
251 }
252
253 std::unique_ptr<WebRtcEventLogHistoryFileReader>
Create(const base::FilePath & path)254 WebRtcEventLogHistoryFileReader::Create(const base::FilePath& path) {
255 auto history_file_reader =
256 base::WrapUnique(new WebRtcEventLogHistoryFileReader(path));
257 if (!history_file_reader->Init()) {
258 LOG(WARNING) << "Initialization of history file reader failed.";
259 return nullptr;
260 }
261 return history_file_reader;
262 }
263
WebRtcEventLogHistoryFileReader(const base::FilePath & path)264 WebRtcEventLogHistoryFileReader::WebRtcEventLogHistoryFileReader(
265 const base::FilePath& path)
266 : path_(path),
267 local_id_(ExtractRemoteBoundWebRtcEventLogLocalIdFromPath(path_)),
268 valid_(false) {}
269
WebRtcEventLogHistoryFileReader(WebRtcEventLogHistoryFileReader && other)270 WebRtcEventLogHistoryFileReader::WebRtcEventLogHistoryFileReader(
271 WebRtcEventLogHistoryFileReader&& other)
272 : path_(other.path_),
273 local_id_(other.local_id_),
274 capture_time_(other.capture_time_),
275 upload_time_(other.upload_time_),
276 upload_id_(other.upload_id_),
277 valid_(other.valid_) {
278 other.valid_ = false;
279 }
280
Init()281 bool WebRtcEventLogHistoryFileReader::Init() {
282 DCHECK(!valid_);
283
284 if (local_id_.empty()) {
285 LOG(WARNING) << "Unknown local ID.";
286 return false;
287 }
288
289 if (local_id_.length() > kWebRtcEventLogMaxUploadIdBytes) {
290 LOG(WARNING) << "Excessively long local ID.";
291 return false;
292 }
293
294 if (!base::PathExists(path_)) {
295 LOG(WARNING) << "File does not exist.";
296 return false;
297 }
298
299 constexpr int file_flags = base::File::FLAG_OPEN | base::File::FLAG_READ;
300 base::File file(path_, file_flags);
301 if (!file.IsValid()) {
302 LOG(WARNING) << "Couldn't read history file.";
303 if (!base::DeleteFile(path_)) {
304 LOG(ERROR) << "Failed to delete " << path_ << ".";
305 }
306 return false;
307 }
308
309 constexpr size_t kMaxHistoryFileSizeBytes = 1024;
310 static_assert(kWebRtcEventLogMaxUploadIdBytes < kMaxHistoryFileSizeBytes, "");
311
312 std::string file_contents;
313 file_contents.resize(kMaxHistoryFileSizeBytes);
314 const int read_bytes = file.Read(0, &file_contents[0], file_contents.size());
315 if (read_bytes < 0) {
316 LOG(WARNING) << "Couldn't read contents of history file.";
317 return false;
318 }
319 DCHECK_LE(static_cast<size_t>(read_bytes), file_contents.size());
320 file_contents.resize(static_cast<size_t>(read_bytes));
321 // Note: In excessively long files, the rest of the file will be ignored; the
322 // beginning of the file will encounter a parse error.
323
324 if (!Parse(file_contents)) {
325 LOG(WARNING) << "Parsing of history file failed.";
326 return false;
327 }
328
329 valid_ = true;
330 return true;
331 }
332
LocalId() const333 std::string WebRtcEventLogHistoryFileReader::LocalId() const {
334 DCHECK(valid_);
335 DCHECK(!local_id_.empty());
336 return local_id_;
337 }
338
CaptureTime() const339 base::Time WebRtcEventLogHistoryFileReader::CaptureTime() const {
340 DCHECK(valid_);
341 DCHECK(!capture_time_.is_null());
342 return capture_time_;
343 }
344
UploadTime() const345 base::Time WebRtcEventLogHistoryFileReader::UploadTime() const {
346 DCHECK(valid_);
347 return upload_time_; // May be null (which indicates "unset").
348 }
349
UploadId() const350 std::string WebRtcEventLogHistoryFileReader::UploadId() const {
351 DCHECK(valid_);
352 return upload_id_;
353 }
354
path() const355 base::FilePath WebRtcEventLogHistoryFileReader::path() const {
356 DCHECK(valid_);
357 return path_;
358 }
359
operator <(const WebRtcEventLogHistoryFileReader & other) const360 bool WebRtcEventLogHistoryFileReader::operator<(
361 const WebRtcEventLogHistoryFileReader& other) const {
362 DCHECK(valid_);
363 DCHECK(!capture_time_.is_null());
364 DCHECK(other.valid_);
365 DCHECK(!other.capture_time_.is_null());
366 if (capture_time_ == other.capture_time_) {
367 // Resolve ties arbitrarily, but consistently (Local IDs are unique).
368 return LocalId() < other.LocalId();
369 }
370 return (capture_time_ < other.capture_time_);
371 }
372
Parse(const std::string & file_contents)373 bool WebRtcEventLogHistoryFileReader::Parse(const std::string& file_contents) {
374 DCHECK(!valid_);
375 DCHECK(capture_time_.is_null());
376 DCHECK(upload_time_.is_null());
377 DCHECK(upload_id_.empty());
378
379 const std::vector<std::string> lines =
380 base::SplitString(file_contents, kEOL, base::TRIM_WHITESPACE,
381 base::SplitResult::SPLIT_WANT_NONEMPTY);
382
383 for (const std::string& line : lines) {
384 if (line.find(kCaptureTimeLinePrefix) == 0) {
385 if (!ParseTime(line, kCaptureTimeLinePrefix, &capture_time_)) {
386 return false;
387 }
388 } else if (line.find(kUploadTimeLinePrefix) == 0) {
389 if (!ParseTime(line, kUploadTimeLinePrefix, &upload_time_)) {
390 return false;
391 }
392 } else if (line.find(kUploadIdLinePrefix) == 0) {
393 if (!ParseString(line, kUploadIdLinePrefix, &upload_id_)) {
394 return false;
395 }
396 } else {
397 LOG(WARNING) << "Unrecognized line in history file.";
398 return false;
399 }
400 }
401
402 if (capture_time_.is_null()) {
403 LOG(WARNING) << "Incomplete history file; capture time unknown.";
404 return false;
405 }
406
407 if (!upload_id_.empty() && upload_time_.is_null()) {
408 LOG(WARNING) << "Incomplete history file; upload time known, "
409 << "but ID unknown.";
410 return false;
411 }
412
413 if (!upload_time_.is_null() && upload_time_ < capture_time_) {
414 LOG(WARNING) << "Defective history file; claims to have been uploaded "
415 << "before being captured.";
416 return false;
417 }
418
419 return true;
420 }
421
422 } // namespace webrtc_event_logging
423