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_common.h"
6
7 #include <cctype>
8 #include <limits>
9
10 #include "base/files/file_util.h"
11 #include "base/logging.h"
12 #include "base/memory/scoped_refptr.h"
13 #include "base/metrics/histogram_functions.h"
14 #include "base/stl_util.h"
15 #include "base/strings/string_number_conversions.h"
16 #include "base/strings/string_piece.h"
17 #include "base/strings/stringprintf.h"
18 #include "base/threading/sequenced_task_runner_handle.h"
19 #include "base/unguessable_token.h"
20 #include "chrome/browser/policy/profile_policy_connector.h"
21 #include "chrome/browser/profiles/profile.h"
22 #include "components/policy/core/common/policy_service.h"
23 #include "content/public/browser/browser_context.h"
24 #include "content/public/browser/browser_thread.h"
25 #include "content/public/browser/render_process_host.h"
26 #include "third_party/zlib/zlib.h"
27
28 #if defined OS_CHROMEOS
29 #include "chrome/browser/chromeos/profiles/profile_helper.h"
30 #endif
31
32 namespace webrtc_event_logging {
33
34 using BrowserContextId = WebRtcEventLogPeerConnectionKey::BrowserContextId;
35
36 const size_t kWebRtcEventLogManagerUnlimitedFileSize = 0;
37
38 const size_t kWebRtcEventLogIdLength = 32;
39
40 // Be careful not to change these without updating the number of characters
41 // reserved in the filename. See kWebAppIdLength.
42 const size_t kMinWebRtcEventLogWebAppId = 1;
43 const size_t kMaxWebRtcEventLogWebAppId = 99;
44
45 // Sentinel value for an invalid web-app ID.
46 const size_t kInvalidWebRtcEventLogWebAppId = 0;
47 static_assert(kInvalidWebRtcEventLogWebAppId < kMinWebRtcEventLogWebAppId ||
48 kInvalidWebRtcEventLogWebAppId > kMaxWebRtcEventLogWebAppId,
49 "Sentinel value must be distinct from legal values.");
50
51 const char kRemoteBoundWebRtcEventLogFileNamePrefix[] = "webrtc_event_log";
52
53 // Important! These values may be relied on by web-apps. Do not change.
54 const char kStartRemoteLoggingFailureAlreadyLogging[] = "Already logging.";
55 const char kStartRemoteLoggingFailureDeadRenderProcessHost[] =
56 "RPH already dead.";
57 const char kStartRemoteLoggingFailureFeatureDisabled[] = "Feature disabled.";
58 const char kStartRemoteLoggingFailureFileCreationError[] =
59 "Could not create file.";
60 const char kStartRemoteLoggingFailureFilePathUsedHistory[] =
61 "Used history file path.";
62 const char kStartRemoteLoggingFailureFilePathUsedLog[] = "Used log file path.";
63 const char kStartRemoteLoggingFailureIllegalWebAppId[] = "Illegal web-app ID.";
64 const char kStartRemoteLoggingFailureLoggingDisabledBrowserContext[] =
65 "Disabled for browser context.";
66 const char kStartRemoteLoggingFailureMaxSizeTooLarge[] =
67 "Excessively large max log size.";
68 const char kStartRemoteLoggingFailureMaxSizeTooSmall[] = "Max size too small.";
69 const char kStartRemoteLoggingFailureNoAdditionalActiveLogsAllowed[] =
70 "No additional active logs allowed.";
71 const char kStartRemoteLoggingFailureOutputPeriodMsTooLarge[] =
72 "Excessively large output period (ms).";
73 const char kStartRemoteLoggingFailureUnknownOrInactivePeerConnection[] =
74 "Unknown or inactive peer connection.";
75 const char kStartRemoteLoggingFailureUnlimitedSizeDisallowed[] =
76 "Unlimited size disallowed.";
77
78 const BrowserContextId kNullBrowserContextId =
79 reinterpret_cast<BrowserContextId>(nullptr);
80
UmaRecordWebRtcEventLoggingApi(WebRtcEventLoggingApiUma result)81 void UmaRecordWebRtcEventLoggingApi(WebRtcEventLoggingApiUma result) {
82 base::UmaHistogramEnumeration("WebRtcEventLogging.Api", result);
83 }
84
UmaRecordWebRtcEventLoggingUpload(WebRtcEventLoggingUploadUma result)85 void UmaRecordWebRtcEventLoggingUpload(WebRtcEventLoggingUploadUma result) {
86 base::UmaHistogramEnumeration("WebRtcEventLogging.Upload", result);
87 }
88
UmaRecordWebRtcEventLoggingNetErrorType(int net_error)89 void UmaRecordWebRtcEventLoggingNetErrorType(int net_error) {
90 base::UmaHistogramSparse("WebRtcEventLogging.NetError", net_error);
91 }
92
93 namespace {
94
95 constexpr int kDefaultMemLevel = 8;
96
97 constexpr size_t kGzipHeaderBytes = 15;
98 constexpr size_t kGzipFooterBytes = 10;
99
100 constexpr size_t kWebAppIdLength = 2;
101
102 // Tracks budget over a resource (such as bytes allowed in a file, etc.).
103 // Allows an unlimited budget.
104 class Budget {
105 public:
106 // If !max.has_value(), the budget is unlimited.
Budget(base::Optional<size_t> max)107 explicit Budget(base::Optional<size_t> max) : max_(max), current_(0) {}
108
109 // Check whether the budget allows consuming an additional |consumed| of
110 // the resource.
ConsumeAllowed(size_t consumed) const111 bool ConsumeAllowed(size_t consumed) const {
112 if (!max_.has_value()) {
113 return true;
114 }
115
116 DCHECK_LE(current_, max_.value());
117
118 const size_t after_consumption = current_ + consumed;
119
120 if (after_consumption < current_) {
121 return false; // Wrap-around.
122 } else if (after_consumption > max_.value()) {
123 return false; // Budget exceeded.
124 } else {
125 return true;
126 }
127 }
128
129 // Checks whether the budget has been completely used up.
Exhausted() const130 bool Exhausted() const { return !ConsumeAllowed(0); }
131
132 // Consume an additional |consumed| of the resource.
Consume(size_t consumed)133 void Consume(size_t consumed) {
134 DCHECK(ConsumeAllowed(consumed));
135 current_ += consumed;
136 }
137
138 private:
139 const base::Optional<size_t> max_;
140 size_t current_;
141 };
142
143 // Writes a log to a file while observing a maximum size.
144 class BaseLogFileWriter : public LogFileWriter {
145 public:
146 // If !max_file_size_bytes.has_value(), an unlimited writer is created.
147 // If it has a value, it must be at least MinFileSizeBytes().
148 BaseLogFileWriter(const base::FilePath& path,
149 base::Optional<size_t> max_file_size_bytes);
150
151 ~BaseLogFileWriter() override;
152
153 bool Init() override;
154
155 const base::FilePath& path() const override;
156
157 bool MaxSizeReached() const override;
158
159 bool Write(const std::string& input) override;
160
161 bool Close() override;
162
163 void Delete() override;
164
165 protected:
166 // * Logs are created PRE_INIT.
167 // * If Init() is successful (potentially writing some header to the log),
168 // the log becomes ACTIVE.
169 // * Any error puts the log into an unrecoverable ERRORED state. When an
170 // errored file is Close()-ed, it is deleted.
171 // * If Write() is ever denied because of budget constraintss, the file
172 // becomes FULL. Only metadata is then allowed (subject to its own budget).
173 // * Closing an ACTIVE or FULL file puts it into CLOSED, at which point the
174 // file may be used. (Note that closing itself might also yield an error,
175 // which would put the file into ERRORED, then deleted.)
176 // * Closed files may be DELETED.
177 enum class State { PRE_INIT, ACTIVE, FULL, CLOSED, ERRORED, DELETED };
178
179 // Setter/getter for |state_|.
180 void SetState(State state);
state() const181 State state() const { return state_; }
182
183 // Checks whether the budget allows writing an additional |bytes|.
184 bool WithinBudget(size_t bytes) const;
185
186 // Writes |input| to the file.
187 // May only be called on ACTIVE or FULL files (for FULL files, only metadata
188 // such as compression footers, etc., may be written; the budget must still
189 // be respected).
190 // It's up to the caller to respect the budget; this will DCHECK on it.
191 // Returns |true| if writing was successful. |false| indicates an
192 // unrecoverable error; the file must be discarded.
193 bool WriteInternal(const std::string& input, bool metadata);
194
195 // Finalizes the file (writes metadata such as compression footer, if any).
196 // Reports whether the file was successfully finalized. Those which weren't
197 // should be discarded.
198 virtual bool Finalize();
199
200 private:
201 scoped_refptr<base::SequencedTaskRunner> task_runner_;
202 const base::FilePath path_;
203 base::File file_; // Populated by Init().
204 State state_;
205 Budget budget_;
206 };
207
BaseLogFileWriter(const base::FilePath & path,base::Optional<size_t> max_file_size_bytes)208 BaseLogFileWriter::BaseLogFileWriter(const base::FilePath& path,
209 base::Optional<size_t> max_file_size_bytes)
210 : task_runner_(base::SequencedTaskRunnerHandle::Get()),
211 path_(path),
212 state_(State::PRE_INIT),
213 budget_(max_file_size_bytes) {}
214
~BaseLogFileWriter()215 BaseLogFileWriter::~BaseLogFileWriter() {
216 if (!task_runner_->RunsTasksInCurrentSequence()) {
217 // Chrome shut-down. The original task_runner_ is no longer running, so
218 // no risk of concurrent access or races.
219 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
220 task_runner_ = base::SequencedTaskRunnerHandle::Get();
221 }
222
223 if (state() != State::CLOSED && state() != State::DELETED) {
224 Close();
225 }
226 }
227
Init()228 bool BaseLogFileWriter::Init() {
229 DCHECK(task_runner_->RunsTasksInCurrentSequence());
230 DCHECK_EQ(state(), State::PRE_INIT);
231
232 // TODO(crbug.com/775415): Use a temporary filename which will indicate
233 // incompletion, and rename to something that is eligible for upload only
234 // on an orderly and successful Close().
235
236 // Attempt to create the file.
237 constexpr int file_flags = base::File::FLAG_CREATE | base::File::FLAG_WRITE |
238 base::File::FLAG_EXCLUSIVE_WRITE;
239 file_.Initialize(path_, file_flags);
240 if (!file_.IsValid() || !file_.created()) {
241 LOG(WARNING) << "Couldn't create remote-bound WebRTC event log file.";
242 if (!base::DeleteFile(path_)) {
243 LOG(ERROR) << "Failed to delete " << path_ << ".";
244 }
245 SetState(State::ERRORED);
246 return false;
247 }
248
249 SetState(State::ACTIVE);
250
251 return true;
252 }
253
path() const254 const base::FilePath& BaseLogFileWriter::path() const {
255 DCHECK(task_runner_->RunsTasksInCurrentSequence());
256 return path_;
257 }
258
MaxSizeReached() const259 bool BaseLogFileWriter::MaxSizeReached() const {
260 DCHECK(task_runner_->RunsTasksInCurrentSequence());
261 DCHECK_EQ(state(), State::ACTIVE);
262 return !WithinBudget(1);
263 }
264
Write(const std::string & input)265 bool BaseLogFileWriter::Write(const std::string& input) {
266 DCHECK(task_runner_->RunsTasksInCurrentSequence());
267 DCHECK_EQ(state(), State::ACTIVE);
268 DCHECK(!MaxSizeReached());
269
270 if (input.empty()) {
271 return true;
272 }
273
274 if (!WithinBudget(input.length())) {
275 SetState(State::FULL);
276 return false;
277 }
278
279 const bool did_write = WriteInternal(input, /*metadata=*/false);
280 if (!did_write) {
281 SetState(State::ERRORED);
282 }
283 return did_write;
284 }
285
Close()286 bool BaseLogFileWriter::Close() {
287 DCHECK(task_runner_->RunsTasksInCurrentSequence());
288 DCHECK_NE(state(), State::CLOSED);
289 DCHECK_NE(state(), State::DELETED);
290
291 const bool result = ((state() != State::ERRORED) && Finalize());
292
293 if (result) {
294 file_.Flush();
295 file_.Close();
296 SetState(State::CLOSED);
297 } else {
298 Delete(); // Changes the state to DELETED.
299 }
300
301 return result;
302 }
303
Delete()304 void BaseLogFileWriter::Delete() {
305 DCHECK(task_runner_->RunsTasksInCurrentSequence());
306 DCHECK_NE(state(), State::DELETED);
307
308 // The file should be closed before deletion. However, we do not want to go
309 // through Finalize() and any potential production of a compression footer,
310 // etc., since we'll be discarding the file anyway.
311 if (state() != State::CLOSED) {
312 file_.Close();
313 }
314
315 if (!base::DeleteFile(path_)) {
316 LOG(ERROR) << "Failed to delete " << path_ << ".";
317 }
318
319 SetState(State::DELETED);
320 }
321
SetState(State state)322 void BaseLogFileWriter::SetState(State state) {
323 DCHECK(task_runner_->RunsTasksInCurrentSequence());
324 state_ = state;
325 }
326
WithinBudget(size_t bytes) const327 bool BaseLogFileWriter::WithinBudget(size_t bytes) const {
328 DCHECK(task_runner_->RunsTasksInCurrentSequence());
329 return budget_.ConsumeAllowed(bytes);
330 }
331
WriteInternal(const std::string & input,bool metadata)332 bool BaseLogFileWriter::WriteInternal(const std::string& input, bool metadata) {
333 DCHECK(task_runner_->RunsTasksInCurrentSequence());
334 DCHECK(state() == State::ACTIVE || (state() == State::FULL && metadata));
335 DCHECK(WithinBudget(input.length()));
336
337 // base::File's interface does not allow writing more than
338 // numeric_limits<int>::max() bytes at a time.
339 DCHECK_LE(input.length(),
340 static_cast<size_t>(std::numeric_limits<int>::max()));
341 const int input_len = static_cast<int>(input.length());
342
343 int written = file_.WriteAtCurrentPos(input.c_str(), input_len);
344 if (written != input_len) {
345 LOG(WARNING) << "WebRTC event log couldn't be written to the "
346 "locally stored file in its entirety.";
347 return false;
348 }
349
350 budget_.Consume(static_cast<size_t>(written));
351
352 return true;
353 }
354
Finalize()355 bool BaseLogFileWriter::Finalize() {
356 DCHECK(task_runner_->RunsTasksInCurrentSequence());
357 DCHECK_NE(state(), State::CLOSED);
358 DCHECK_NE(state(), State::DELETED);
359 DCHECK_NE(state(), State::ERRORED);
360 return true;
361 }
362
363 // Writes a GZIP-compressed log to a file while observing a maximum size.
364 class GzippedLogFileWriter : public BaseLogFileWriter {
365 public:
366 GzippedLogFileWriter(const base::FilePath& path,
367 base::Optional<size_t> max_file_size_bytes,
368 std::unique_ptr<LogCompressor> compressor);
369
370 ~GzippedLogFileWriter() override = default;
371
372 bool Init() override;
373
374 bool MaxSizeReached() const override;
375
376 bool Write(const std::string& input) override;
377
378 protected:
379 bool Finalize() override;
380
381 private:
382 std::unique_ptr<LogCompressor> compressor_;
383 };
384
GzippedLogFileWriter(const base::FilePath & path,base::Optional<size_t> max_file_size_bytes,std::unique_ptr<LogCompressor> compressor)385 GzippedLogFileWriter::GzippedLogFileWriter(
386 const base::FilePath& path,
387 base::Optional<size_t> max_file_size_bytes,
388 std::unique_ptr<LogCompressor> compressor)
389 : BaseLogFileWriter(path, max_file_size_bytes),
390 compressor_(std::move(compressor)) {
391 // Factory validates size before instantiation.
392 DCHECK(!max_file_size_bytes.has_value() ||
393 max_file_size_bytes.value() >= kGzipOverheadBytes);
394 }
395
Init()396 bool GzippedLogFileWriter::Init() {
397 if (!BaseLogFileWriter::Init()) {
398 // Super-class should SetState on its own.
399 return false;
400 }
401
402 std::string header;
403 compressor_->CreateHeader(&header);
404
405 const bool result = WriteInternal(header, /*metadata=*/true);
406 if (!result) {
407 SetState(State::ERRORED);
408 }
409
410 return result;
411 }
412
MaxSizeReached() const413 bool GzippedLogFileWriter::MaxSizeReached() const {
414 DCHECK_EQ(state(), State::ACTIVE);
415
416 // Note that the overhead used (footer only) assumes state() is State::ACTIVE,
417 // as DCHECKed above.
418 return !WithinBudget(1 + kGzipFooterBytes);
419 }
420
Write(const std::string & input)421 bool GzippedLogFileWriter::Write(const std::string& input) {
422 DCHECK_EQ(state(), State::ACTIVE);
423 DCHECK(!MaxSizeReached());
424
425 if (input.empty()) {
426 return true;
427 }
428
429 std::string compressed_input;
430 const auto result = compressor_->Compress(input, &compressed_input);
431
432 switch (result) {
433 case LogCompressor::Result::OK: {
434 // |compressor_| guarantees |compressed_input| is within-budget.
435 bool did_write = WriteInternal(compressed_input, /*metadata=*/false);
436 if (!did_write) {
437 SetState(State::ERRORED);
438 }
439 return did_write;
440 }
441 case LogCompressor::Result::DISALLOWED: {
442 SetState(State::FULL);
443 return false;
444 }
445 case LogCompressor::Result::ERROR_ENCOUNTERED: {
446 SetState(State::ERRORED);
447 return false;
448 }
449 }
450
451 NOTREACHED();
452 return false; // Appease compiler.
453 }
454
Finalize()455 bool GzippedLogFileWriter::Finalize() {
456 DCHECK_NE(state(), State::CLOSED);
457 DCHECK_NE(state(), State::DELETED);
458 DCHECK_NE(state(), State::ERRORED);
459
460 std::string footer;
461 if (!compressor_->CreateFooter(&footer)) {
462 LOG(WARNING) << "Compression footer could not be produced.";
463 SetState(State::ERRORED);
464 return false;
465 }
466
467 // |compressor_| guarantees |footer| is within-budget.
468 if (!WriteInternal(footer, /*metadata=*/true)) {
469 LOG(WARNING) << "Footer could not be written.";
470 SetState(State::ERRORED);
471 return false;
472 }
473
474 return true;
475 }
476
477 // Concrete implementation of LogCompressor using GZIP.
478 class GzipLogCompressor : public LogCompressor {
479 public:
480 GzipLogCompressor(
481 base::Optional<size_t> max_size_bytes,
482 std::unique_ptr<CompressedSizeEstimator> compressed_size_estimator);
483
484 ~GzipLogCompressor() override;
485
486 void CreateHeader(std::string* output) override;
487
488 Result Compress(const std::string& input, std::string* output) override;
489
490 bool CreateFooter(std::string* output) override;
491
492 private:
493 // * A compressed log starts out empty (PRE_HEADER).
494 // * Once the header is produced, the stream is ACTIVE.
495 // * If it is ever detected that compressing the next input would exceed the
496 // budget, that input is NOT compressed, and the state becomes FULL, from
497 // which only writing the footer or discarding the file are allowed.
498 // * Writing the footer is allowed on an ACTIVE or FULL stream. Then, the
499 // stream is effectively closed.
500 // * Any error puts the stream into ERRORED. An errored stream can only
501 // be discarded.
502 enum class State { PRE_HEADER, ACTIVE, FULL, POST_FOOTER, ERRORED };
503
504 // Returns the budget left after reserving the GZIP overhead.
505 // Optionals without a value, both in the parameters as well as in the
506 // return value of the function, signal an unlimited amount.
507 static base::Optional<size_t> SizeAfterOverheadReservation(
508 base::Optional<size_t> max_size_bytes);
509
510 // Compresses |input| into |output|, while observing the budget (unless
511 // !budgeted). If |last|, also closes the stream.
512 Result CompressInternal(const std::string& input,
513 std::string* output,
514 bool budgeted,
515 bool last);
516
517 // Compresses the input data already in |stream_| into |output|.
518 bool Deflate(int flush, std::string* output);
519
520 State state_;
521 Budget budget_;
522 std::unique_ptr<CompressedSizeEstimator> compressed_size_estimator_;
523 z_stream stream_;
524 };
525
GzipLogCompressor(base::Optional<size_t> max_size_bytes,std::unique_ptr<CompressedSizeEstimator> compressed_size_estimator)526 GzipLogCompressor::GzipLogCompressor(
527 base::Optional<size_t> max_size_bytes,
528 std::unique_ptr<CompressedSizeEstimator> compressed_size_estimator)
529 : state_(State::PRE_HEADER),
530 budget_(SizeAfterOverheadReservation(max_size_bytes)),
531 compressed_size_estimator_(std::move(compressed_size_estimator)) {
532 memset(&stream_, 0, sizeof(z_stream));
533 // Using (MAX_WBITS + 16) triggers the creation of a GZIP header.
534 const int result =
535 deflateInit2(&stream_, Z_DEFAULT_COMPRESSION, Z_DEFLATED, MAX_WBITS + 16,
536 kDefaultMemLevel, Z_DEFAULT_STRATEGY);
537 DCHECK_EQ(result, Z_OK);
538 }
539
~GzipLogCompressor()540 GzipLogCompressor::~GzipLogCompressor() {
541 const int result = deflateEnd(&stream_);
542 // Z_DATA_ERROR reports that the stream was not properly terminated,
543 // but nevertheless correctly released. That happens when we don't
544 // write the footer.
545 DCHECK(result == Z_OK ||
546 (result == Z_DATA_ERROR && state_ != State::POST_FOOTER));
547 }
548
CreateHeader(std::string * output)549 void GzipLogCompressor::CreateHeader(std::string* output) {
550 DCHECK(output);
551 DCHECK(output->empty());
552 DCHECK_EQ(state_, State::PRE_HEADER);
553
554 const Result result = CompressInternal(std::string(), output,
555 /*budgeted=*/false, /*last=*/false);
556 DCHECK_EQ(result, Result::OK);
557 DCHECK_EQ(output->size(), kGzipHeaderBytes);
558
559 state_ = State::ACTIVE;
560 }
561
Compress(const std::string & input,std::string * output)562 LogCompressor::Result GzipLogCompressor::Compress(const std::string& input,
563 std::string* output) {
564 DCHECK_EQ(state_, State::ACTIVE);
565
566 if (input.empty()) {
567 return Result::OK;
568 }
569
570 const auto result =
571 CompressInternal(input, output, /*budgeted=*/true, /*last=*/false);
572
573 switch (result) {
574 case Result::OK:
575 return result;
576 case Result::DISALLOWED:
577 state_ = State::FULL;
578 return result;
579 case Result::ERROR_ENCOUNTERED:
580 state_ = State::ERRORED;
581 return result;
582 }
583
584 NOTREACHED();
585 return Result::ERROR_ENCOUNTERED; // Appease compiler.
586 }
587
CreateFooter(std::string * output)588 bool GzipLogCompressor::CreateFooter(std::string* output) {
589 DCHECK(output);
590 DCHECK(output->empty());
591 DCHECK(state_ == State::ACTIVE || state_ == State::FULL);
592
593 const Result result = CompressInternal(std::string(), output,
594 /*budgeted=*/false, /*last=*/true);
595 if (result != Result::OK) { // !budgeted -> Result::DISALLOWED impossible.
596 DCHECK_EQ(result, Result::ERROR_ENCOUNTERED);
597 // An error message was logged by CompressInternal().
598 state_ = State::ERRORED;
599 return false;
600 }
601
602 if (output->length() != kGzipFooterBytes) {
603 LOG(ERROR) << "Incorrect footer size (" << output->length() << ").";
604 state_ = State::ERRORED;
605 return false;
606 }
607
608 state_ = State::POST_FOOTER;
609
610 return true;
611 }
612
SizeAfterOverheadReservation(base::Optional<size_t> max_size_bytes)613 base::Optional<size_t> GzipLogCompressor::SizeAfterOverheadReservation(
614 base::Optional<size_t> max_size_bytes) {
615 if (!max_size_bytes.has_value()) {
616 return base::Optional<size_t>();
617 } else {
618 DCHECK_GE(max_size_bytes.value(), kGzipHeaderBytes + kGzipFooterBytes);
619 return max_size_bytes.value() - (kGzipHeaderBytes + kGzipFooterBytes);
620 }
621 }
622
CompressInternal(const std::string & input,std::string * output,bool budgeted,bool last)623 LogCompressor::Result GzipLogCompressor::CompressInternal(
624 const std::string& input,
625 std::string* output,
626 bool budgeted,
627 bool last) {
628 DCHECK(output);
629 DCHECK(output->empty());
630 DCHECK(state_ == State::PRE_HEADER || state_ == State::ACTIVE ||
631 (!budgeted && state_ == State::FULL));
632
633 // Avoid writing to |output| unless the return value is OK.
634 std::string temp_output;
635
636 if (budgeted) {
637 const size_t estimated_compressed_size =
638 compressed_size_estimator_->EstimateCompressedSize(input);
639 if (!budget_.ConsumeAllowed(estimated_compressed_size)) {
640 return Result::DISALLOWED;
641 }
642 }
643
644 if (last) {
645 DCHECK(input.empty());
646 stream_.next_in = nullptr;
647 } else {
648 stream_.next_in = reinterpret_cast<z_const Bytef*>(input.c_str());
649 }
650
651 DCHECK_LE(input.length(),
652 static_cast<size_t>(std::numeric_limits<uInt>::max()));
653 stream_.avail_in = static_cast<uInt>(input.length());
654
655 const bool result = Deflate(last ? Z_FINISH : Z_SYNC_FLUSH, &temp_output);
656
657 stream_.next_in = nullptr; // Avoid dangling pointers.
658
659 if (!result) {
660 // An error message was logged by Deflate().
661 return Result::ERROR_ENCOUNTERED;
662 }
663
664 if (budgeted) {
665 if (!budget_.ConsumeAllowed(temp_output.length())) {
666 LOG(WARNING) << "Compressed size was above estimate and unexpectedly "
667 "exceeded the budget.";
668 return Result::ERROR_ENCOUNTERED;
669 }
670 budget_.Consume(temp_output.length());
671 }
672
673 std::swap(*output, temp_output);
674 return Result::OK;
675 }
676
Deflate(int flush,std::string * output)677 bool GzipLogCompressor::Deflate(int flush, std::string* output) {
678 DCHECK((flush != Z_FINISH && stream_.next_in != nullptr) ||
679 (flush == Z_FINISH && stream_.next_in == nullptr));
680 DCHECK(output->empty());
681
682 bool success = true; // Result of this method.
683 int z_result; // Result of the zlib function.
684
685 size_t total_compressed_size = 0;
686
687 do {
688 // Allocate some additional buffer.
689 constexpr uInt kCompressionBuffer = 4 * 1024;
690 output->resize(total_compressed_size + kCompressionBuffer);
691
692 // This iteration should write directly beyond previous iterations' last
693 // written byte.
694 stream_.next_out =
695 reinterpret_cast<uint8_t*>(&((*output)[total_compressed_size]));
696 stream_.avail_out = kCompressionBuffer;
697
698 z_result = deflate(&stream_, flush);
699
700 DCHECK_GE(kCompressionBuffer, stream_.avail_out);
701 const size_t compressed_size = kCompressionBuffer - stream_.avail_out;
702
703 if (flush != Z_FINISH) {
704 if (z_result != Z_OK) {
705 LOG(ERROR) << "Compression failed (" << z_result << ").";
706 success = false;
707 break;
708 }
709 } else { // flush == Z_FINISH
710 // End of the stream; we expect the footer to be exactly the size which
711 // we've set aside for it.
712 if (z_result != Z_STREAM_END || compressed_size != kGzipFooterBytes) {
713 LOG(ERROR) << "Compression failed (" << z_result << ", "
714 << compressed_size << ").";
715 success = false;
716 break;
717 }
718 }
719
720 total_compressed_size += compressed_size;
721 } while (stream_.avail_out == 0 && z_result != Z_STREAM_END);
722
723 stream_.next_out = nullptr; // Avoid dangling pointers.
724
725 if (success) {
726 output->resize(total_compressed_size);
727 } else {
728 output->clear();
729 }
730
731 return success;
732 }
733
734 // Given a string with a textual representation of a web-app ID, return the
735 // ID in integer form. If the textual representation does not name a valid
736 // web-app ID, return kInvalidWebRtcEventLogWebAppId.
ExtractWebAppId(base::StringPiece str)737 size_t ExtractWebAppId(base::StringPiece str) {
738 DCHECK_EQ(str.length(), kWebAppIdLength);
739
740 // Avoid leading '+', etc.
741 for (size_t i = 0; i < str.length(); i++) {
742 if (!std::isdigit(str[i])) {
743 return kInvalidWebRtcEventLogWebAppId;
744 }
745 }
746
747 size_t result;
748 if (!base::StringToSizeT(str, &result) ||
749 result < kMinWebRtcEventLogWebAppId ||
750 result > kMaxWebRtcEventLogWebAppId) {
751 return kInvalidWebRtcEventLogWebAppId;
752 }
753 return result;
754 }
755
756 } // namespace
757
758 const size_t kGzipOverheadBytes = kGzipHeaderBytes + kGzipFooterBytes;
759
760 const base::FilePath::CharType kWebRtcEventLogUncompressedExtension[] =
761 FILE_PATH_LITERAL("log");
762 const base::FilePath::CharType kWebRtcEventLogGzippedExtension[] =
763 FILE_PATH_LITERAL("log.gz");
764 const base::FilePath::CharType kWebRtcEventLogHistoryExtension[] =
765 FILE_PATH_LITERAL("hist");
766
MinFileSizeBytes() const767 size_t BaseLogFileWriterFactory::MinFileSizeBytes() const {
768 // No overhead incurred; data written straight to the file without metadata.
769 return 0;
770 }
771
Extension() const772 base::FilePath::StringPieceType BaseLogFileWriterFactory::Extension() const {
773 return kWebRtcEventLogUncompressedExtension;
774 }
775
Create(const base::FilePath & path,base::Optional<size_t> max_file_size_bytes) const776 std::unique_ptr<LogFileWriter> BaseLogFileWriterFactory::Create(
777 const base::FilePath& path,
778 base::Optional<size_t> max_file_size_bytes) const {
779 if (max_file_size_bytes.has_value() &&
780 max_file_size_bytes.value() < MinFileSizeBytes()) {
781 LOG(WARNING) << "Max size (" << max_file_size_bytes.value()
782 << ") below minimum size (" << MinFileSizeBytes() << ").";
783 return nullptr;
784 }
785
786 auto result = std::make_unique<BaseLogFileWriter>(path, max_file_size_bytes);
787
788 if (!result->Init()) {
789 // Error logged by Init.
790 result.reset(); // Destructor deletes errored files.
791 }
792
793 return result;
794 }
795
796 std::unique_ptr<CompressedSizeEstimator>
Create() const797 DefaultGzippedSizeEstimator::Factory::Create() const {
798 return std::make_unique<DefaultGzippedSizeEstimator>();
799 }
800
EstimateCompressedSize(const std::string & input) const801 size_t DefaultGzippedSizeEstimator::EstimateCompressedSize(
802 const std::string& input) const {
803 // This estimation is not tight. Since we expect to produce logs of
804 // several MBs, overshooting the estimation by one KB should be
805 // very safe and still relatively efficient.
806 constexpr size_t kOverheadOverUncompressedSizeBytes = 1000;
807 return input.length() + kOverheadOverUncompressedSizeBytes;
808 }
809
GzipLogCompressorFactory(std::unique_ptr<CompressedSizeEstimator::Factory> estimator_factory)810 GzipLogCompressorFactory::GzipLogCompressorFactory(
811 std::unique_ptr<CompressedSizeEstimator::Factory> estimator_factory)
812 : estimator_factory_(std::move(estimator_factory)) {}
813
814 GzipLogCompressorFactory::~GzipLogCompressorFactory() = default;
815
MinSizeBytes() const816 size_t GzipLogCompressorFactory::MinSizeBytes() const {
817 return kGzipOverheadBytes;
818 }
819
Create(base::Optional<size_t> max_size_bytes) const820 std::unique_ptr<LogCompressor> GzipLogCompressorFactory::Create(
821 base::Optional<size_t> max_size_bytes) const {
822 if (max_size_bytes.has_value() && max_size_bytes.value() < MinSizeBytes()) {
823 LOG(WARNING) << "Max size (" << max_size_bytes.value()
824 << ") below minimum size (" << MinSizeBytes() << ").";
825 return nullptr;
826 }
827 return std::make_unique<GzipLogCompressor>(max_size_bytes,
828 estimator_factory_->Create());
829 }
830
GzippedLogFileWriterFactory(std::unique_ptr<GzipLogCompressorFactory> gzip_compressor_factory)831 GzippedLogFileWriterFactory::GzippedLogFileWriterFactory(
832 std::unique_ptr<GzipLogCompressorFactory> gzip_compressor_factory)
833 : gzip_compressor_factory_(std::move(gzip_compressor_factory)) {}
834
835 GzippedLogFileWriterFactory::~GzippedLogFileWriterFactory() = default;
836
MinFileSizeBytes() const837 size_t GzippedLogFileWriterFactory::MinFileSizeBytes() const {
838 // Only the compression's own overhead is incurred.
839 return gzip_compressor_factory_->MinSizeBytes();
840 }
841
Extension() const842 base::FilePath::StringPieceType GzippedLogFileWriterFactory::Extension() const {
843 return kWebRtcEventLogGzippedExtension;
844 }
845
Create(const base::FilePath & path,base::Optional<size_t> max_file_size_bytes) const846 std::unique_ptr<LogFileWriter> GzippedLogFileWriterFactory::Create(
847 const base::FilePath& path,
848 base::Optional<size_t> max_file_size_bytes) const {
849 if (max_file_size_bytes.has_value() &&
850 max_file_size_bytes.value() < MinFileSizeBytes()) {
851 LOG(WARNING) << "Size below allowed minimum.";
852 return nullptr;
853 }
854
855 auto gzip_compressor = gzip_compressor_factory_->Create(max_file_size_bytes);
856 if (!gzip_compressor) {
857 // The factory itself will have logged an error.
858 return nullptr;
859 }
860
861 auto result = std::make_unique<GzippedLogFileWriter>(
862 path, max_file_size_bytes, std::move(gzip_compressor));
863
864 if (!result->Init()) {
865 // Error logged by Init.
866 result.reset(); // Destructor deletes errored files.
867 }
868
869 return result;
870 }
871
872 // Create a random identifier of 32 hexadecimal (uppercase) characters.
CreateWebRtcEventLogId()873 std::string CreateWebRtcEventLogId() {
874 // UnguessableToken's interface makes no promisses over case. We therefore
875 // convert, even if the current implementation does not require it.
876 std::string log_id =
877 base::ToUpperASCII(base::UnguessableToken::Create().ToString());
878 DCHECK_EQ(log_id.size(), kWebRtcEventLogIdLength);
879 DCHECK_EQ(log_id.find_first_not_of("0123456789ABCDEF"), std::string::npos);
880 return log_id;
881 }
882
GetBrowserContextId(const content::BrowserContext * browser_context)883 BrowserContextId GetBrowserContextId(
884 const content::BrowserContext* browser_context) {
885 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
886 return reinterpret_cast<BrowserContextId>(browser_context);
887 }
888
GetBrowserContextId(int render_process_id)889 BrowserContextId GetBrowserContextId(int render_process_id) {
890 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
891
892 content::RenderProcessHost* const host =
893 content::RenderProcessHost::FromID(render_process_id);
894
895 content::BrowserContext* const browser_context =
896 host ? host->GetBrowserContext() : nullptr;
897
898 return GetBrowserContextId(browser_context);
899 }
900
GetRemoteBoundWebRtcEventLogsDir(const base::FilePath & browser_context_dir)901 base::FilePath GetRemoteBoundWebRtcEventLogsDir(
902 const base::FilePath& browser_context_dir) {
903 const base::FilePath::CharType kRemoteBoundLogSubDirectory[] =
904 FILE_PATH_LITERAL("webrtc_event_logs");
905 return browser_context_dir.Append(kRemoteBoundLogSubDirectory);
906 }
907
WebRtcEventLogPath(const base::FilePath & remote_logs_dir,const std::string & log_id,size_t web_app_id,const base::FilePath::StringPieceType & extension)908 base::FilePath WebRtcEventLogPath(
909 const base::FilePath& remote_logs_dir,
910 const std::string& log_id,
911 size_t web_app_id,
912 const base::FilePath::StringPieceType& extension) {
913 DCHECK_GE(web_app_id, kMinWebRtcEventLogWebAppId);
914 DCHECK_LE(web_app_id, kMaxWebRtcEventLogWebAppId);
915
916 static_assert(kWebAppIdLength == 2u, "Fix the code below.");
917 const std::string web_app_id_str = base::StringPrintf("%02zu", web_app_id);
918 DCHECK_EQ(web_app_id_str.length(), kWebAppIdLength);
919
920 const std::string filename =
921 std::string(kRemoteBoundWebRtcEventLogFileNamePrefix) + "_" +
922 web_app_id_str + "_" + log_id;
923
924 return remote_logs_dir.AppendASCII(filename).AddExtension(extension);
925 }
926
IsValidRemoteBoundLogFilename(const std::string & filename)927 bool IsValidRemoteBoundLogFilename(const std::string& filename) {
928 // The -1 is because of the implict \0.
929 const size_t kPrefixLength =
930 base::size(kRemoteBoundWebRtcEventLogFileNamePrefix) - 1;
931
932 // [prefix]_[web_app_id]_[log_id]
933 const size_t expected_length =
934 kPrefixLength + 1 + kWebAppIdLength + 1 + kWebRtcEventLogIdLength;
935 if (filename.length() != expected_length) {
936 return false;
937 }
938
939 size_t index = 0;
940
941 // Expect prefix.
942 if (filename.find(kRemoteBoundWebRtcEventLogFileNamePrefix) != index) {
943 return false;
944 }
945 index += kPrefixLength;
946
947 // Expect underscore between prefix and web-app ID.
948 if (filename[index] != '_') {
949 return false;
950 }
951 index += 1;
952
953 // Expect web-app-ID.
954 const size_t web_app_id =
955 ExtractWebAppId(base::StringPiece(&filename[index], kWebAppIdLength));
956 if (web_app_id == kInvalidWebRtcEventLogWebAppId) {
957 return false;
958 }
959 index += kWebAppIdLength;
960
961 // Expect underscore between web-app ID and log ID.
962 if (filename[index] != '_') {
963 return false;
964 }
965 index += 1;
966
967 // Expect log ID.
968 const std::string log_id = filename.substr(index);
969 DCHECK_EQ(log_id.length(), kWebRtcEventLogIdLength);
970 const char* const log_id_chars = "0123456789ABCDEF";
971 if (filename.find_first_not_of(log_id_chars, index) != std::string::npos) {
972 return false;
973 }
974
975 return true;
976 }
977
IsValidRemoteBoundLogFilePath(const base::FilePath & path)978 bool IsValidRemoteBoundLogFilePath(const base::FilePath& path) {
979 const std::string filename = path.BaseName().RemoveExtension().MaybeAsASCII();
980 return IsValidRemoteBoundLogFilename(filename);
981 }
982
GetWebRtcEventLogHistoryFilePath(const base::FilePath & path)983 base::FilePath GetWebRtcEventLogHistoryFilePath(const base::FilePath& path) {
984 // TODO(crbug.com/775415): Check for validity (after fixing unit tests).
985 return path.RemoveExtension().AddExtension(kWebRtcEventLogHistoryExtension);
986 }
987
ExtractRemoteBoundWebRtcEventLogLocalIdFromPath(const base::FilePath & path)988 std::string ExtractRemoteBoundWebRtcEventLogLocalIdFromPath(
989 const base::FilePath& path) {
990 const std::string filename = path.BaseName().RemoveExtension().MaybeAsASCII();
991 if (!IsValidRemoteBoundLogFilename(filename)) {
992 LOG(WARNING) << "Invalid remote-bound WebRTC event log filename.";
993 return std::string();
994 }
995
996 DCHECK_GE(filename.length(), kWebRtcEventLogIdLength);
997 return filename.substr(filename.length() - kWebRtcEventLogIdLength);
998 }
999
ExtractRemoteBoundWebRtcEventLogWebAppIdFromPath(const base::FilePath & path)1000 size_t ExtractRemoteBoundWebRtcEventLogWebAppIdFromPath(
1001 const base::FilePath& path) {
1002 const std::string filename = path.BaseName().RemoveExtension().MaybeAsASCII();
1003 if (!IsValidRemoteBoundLogFilename(filename)) {
1004 LOG(WARNING) << "Invalid remote-bound WebRTC event log filename.";
1005 return kInvalidWebRtcEventLogWebAppId;
1006 }
1007
1008 // The -1 is because of the implict \0.
1009 const size_t kPrefixLength =
1010 base::size(kRemoteBoundWebRtcEventLogFileNamePrefix) - 1;
1011
1012 // The +1 is for the underscore between the prefix and the web-app ID.
1013 // Length verified by above call to IsValidRemoteBoundLogFilename().
1014 DCHECK_GE(filename.length(), kPrefixLength + 1 + kWebAppIdLength);
1015 base::StringPiece id_str(&filename[kPrefixLength + 1], kWebAppIdLength);
1016
1017 return ExtractWebAppId(id_str);
1018 }
1019
DoesProfileDefaultToLoggingEnabled(const Profile * const profile)1020 bool DoesProfileDefaultToLoggingEnabled(const Profile* const profile) {
1021 // For Chrome OS, exclude special profiles and users.
1022 #if defined(OS_CHROMEOS)
1023 const user_manager::User* user =
1024 chromeos::ProfileHelper::Get()->GetUserByProfile(profile);
1025 // We do not log an error here since this can happen in several cases,
1026 // e.g. for signin profiles or lock screen app profiles.
1027 if (!user) {
1028 return false;
1029 }
1030 const user_manager::UserType user_type = user->GetType();
1031 if (user_type != user_manager::USER_TYPE_REGULAR) {
1032 return false;
1033 }
1034 if (chromeos::ProfileHelper::IsEphemeralUserProfile(profile)) {
1035 return false;
1036 }
1037 #endif
1038
1039 // We only want a default of true for regular (i.e. logged-in) profiles
1040 // receiving cloud-based user-level enterprise policies. Supervised (child)
1041 // profiles are considered regular and can also receive cloud policies in some
1042 // cases (e.g. on Chrome OS). Although currently this should be covered by the
1043 // other checks, let's explicitly check to anticipate edge cases and make the
1044 // requirement explicit.
1045 if (!profile->IsRegularProfile() || profile->IsSupervised()) {
1046 return false;
1047 }
1048
1049 const policy::ProfilePolicyConnector* const policy_connector =
1050 profile->GetProfilePolicyConnector();
1051
1052 return policy_connector->policy_service()->IsInitializationComplete(
1053 policy::POLICY_DOMAIN_CHROME) &&
1054 policy_connector->IsManaged();
1055 }
1056
1057 } // namespace webrtc_event_logging
1058