summaryrefslogtreecommitdiff
path: root/chromium/chrome/browser/media/webrtc/webrtc_event_log_uploader.cc
blob: 17f542efa756188c5a7520ca6b37a1ac532c7359 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/media/webrtc/webrtc_event_log_uploader.h"

#include "base/bind.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/strings/stringprintf.h"
#include "base/task/post_task.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "components/version_info/version_info.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "net/base/load_flags.h"
#include "net/base/mime_util.h"
#include "net/http/http_status_code.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "ui/base/text/bytes_formatting.h"

namespace webrtc_event_logging {

namespace {
// TODO(crbug.com/817495): Eliminate the duplication with other uploaders.
const char kUploadContentType[] = "multipart/form-data";
const char kBoundary[] = "----**--yradnuoBgoLtrapitluMklaTelgooG--**----";

constexpr size_t kExpectedMimeOverheadBytes = 1000;  // Intentional overshot.

// TODO(crbug.com/817495): Eliminate the duplication with other uploaders.
#if defined(OS_WIN)
const char kProduct[] = "Chrome";
#elif defined(OS_MACOSX)
const char kProduct[] = "Chrome_Mac";
#elif defined(OS_LINUX)
const char kProduct[] = "Chrome_Linux";
#elif defined(OS_ANDROID)
const char kProduct[] = "Chrome_Android";
#elif defined(OS_CHROMEOS)
const char kProduct[] = "Chrome_ChromeOS";
#else
#error Platform not supported.
#endif

constexpr net::NetworkTrafficAnnotationTag
    kWebrtcEventLogUploaderTrafficAnnotation =
        net::DefineNetworkTrafficAnnotation("webrtc_event_log_uploader", R"(
      semantics {
        sender: "WebRTC Event Log uploader module"
        description:
          "Uploads a WebRTC event log to a server called Crash. These logs "
          "will not contain private information. They will be used to "
          "improve WebRTC (fix bugs, tune performance, etc.)."
        trigger:
          "A Google service (e.g. Hangouts/Meet) has requested a peer "
          "connection to be logged, and the resulting event log to be uploaded "
          "at a time deemed to cause the least interference to the user (i.e., "
          "when the user is not busy making other VoIP calls)."
        data:
          "WebRTC events such as the timing of audio playout (but not the "
          "content), timing and size of RTP packets sent/received, etc."
        destination: GOOGLE_OWNED_SERVICE
      }
      policy {
        cookies_allowed: NO
        setting: "Feature controlled only through Chrome policy; "
                 "no user-facing control surface."
        chrome_policy {
          WebRtcEventLogCollectionAllowed {
            WebRtcEventLogCollectionAllowed: false
          }
        }
      })");

void AddFileContents(const char* filename,
                     const std::string& file_contents,
                     const std::string& content_type,
                     std::string* post_data) {
  // net::AddMultipartValueForUpload does almost what we want to do here, except
  // that it does not add the "filename" attribute. We hack it to force it to.
  std::string mime_value_name =
      base::StringPrintf("%s\"; filename=\"%s\"", filename, filename);
  net::AddMultipartValueForUpload(mime_value_name, file_contents, kBoundary,
                                  content_type, post_data);
}

std::string MimeContentType() {
  const char kBoundaryKeywordAndMisc[] = "; boundary=";

  std::string content_type;
  content_type.reserve(sizeof(content_type) + sizeof(kBoundaryKeywordAndMisc) +
                       sizeof(kBoundary));

  content_type.append(kUploadContentType);
  content_type.append(kBoundaryKeywordAndMisc);
  content_type.append(kBoundary);

  return content_type;
}

void BindURLLoaderFactoryReceiver(
    mojo::PendingReceiver<network::mojom::URLLoaderFactory>
        url_loader_factory_receiver) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory =
      g_browser_process->shared_url_loader_factory();
  DCHECK(shared_url_loader_factory);
  shared_url_loader_factory->Clone(std::move(url_loader_factory_receiver));
}

void OnURLLoadUploadProgress(uint64_t current, uint64_t total) {
  ui::DataUnits unit = ui::GetByteDisplayUnits(total);
  VLOG(1) << "WebRTC event log upload progress: "
          << FormatBytesWithUnits(current, unit, false) << " / "
          << FormatBytesWithUnits(total, unit, true) << ".";
}
}  // namespace

const char WebRtcEventLogUploaderImpl::kUploadURL[] =
    "https://clients2.google.com/cr/report";

std::unique_ptr<WebRtcEventLogUploader>
WebRtcEventLogUploaderImpl::Factory::Create(const WebRtcLogFileInfo& log_file,
                                            UploadResultCallback callback) {
  return std::make_unique<WebRtcEventLogUploaderImpl>(
      log_file, std::move(callback), kMaxRemoteLogFileSizeBytes);
}

std::unique_ptr<WebRtcEventLogUploader>
WebRtcEventLogUploaderImpl::Factory::CreateWithCustomMaxSizeForTesting(
    const WebRtcLogFileInfo& log_file,
    UploadResultCallback callback,
    size_t max_log_file_size_bytes) {
  return std::make_unique<WebRtcEventLogUploaderImpl>(
      log_file, std::move(callback), max_log_file_size_bytes);
}

WebRtcEventLogUploaderImpl::WebRtcEventLogUploaderImpl(
    const WebRtcLogFileInfo& log_file,
    UploadResultCallback callback,
    size_t max_log_file_size_bytes)
    : log_file_(log_file),
      callback_(std::move(callback)),
      max_log_file_size_bytes_(max_log_file_size_bytes),
      io_task_runner_(base::SequencedTaskRunnerHandle::Get()) {
  history_file_writer_ = WebRtcEventLogHistoryFileWriter::Create(
      GetWebRtcEventLogHistoryFilePath(log_file_.path));
  if (!history_file_writer_) {
    // File either could not be created, or, if a different error occurred,
    // Create() will have tried to remove the file it has created.
    UmaRecordWebRtcEventLoggingUpload(
        WebRtcEventLoggingUploadUma::kHistoryFileCreationError);
    ReportResult(false);
    return;
  }

  const base::Time now = std::max(base::Time::Now(), log_file.last_modified);
  if (!history_file_writer_->WriteCaptureTime(log_file.last_modified) ||
      !history_file_writer_->WriteUploadTime(now)) {
    LOG(ERROR) << "Writing to history file failed.";
    UmaRecordWebRtcEventLoggingUpload(
        WebRtcEventLoggingUploadUma::kHistoryFileWriteError);
    DeleteHistoryFile();  // Avoid partial, potentially-corrupt history files.
    ReportResult(false);
    return;
  }

  std::string upload_data;
  if (!PrepareUploadData(&upload_data)) {
    // History file will reflect a failed upload attempt.
    ReportResult(false);  // UMA recorded by PrepareUploadData().
    return;
  }

  StartUpload(upload_data);
}

WebRtcEventLogUploaderImpl::~WebRtcEventLogUploaderImpl() {
  // WebRtcEventLogUploaderImpl objects' deletion scenarios:
  // 1. Upload started and finished - |url_loader_| should have been reset
  //    so that we would be able to DCHECK and demonstrate that the determinant
  //    is maintained.
  // 2. Upload started and cancelled - behave similarly to a finished upload.
  // 3. The upload was never started, due to an early failure (e.g. file not
  //    found). In that case, |url_loader_| will not have been set.
  // 4. Chrome shutdown.
  if (io_task_runner_->RunsTasksInCurrentSequence()) {  // Scenarios 1-3.
    DCHECK(!url_loader_);
  } else {  // # Scenario #4 - Chrome shutdown.
    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
    bool will_delete =
        io_task_runner_->DeleteSoon(FROM_HERE, url_loader_.release());
    DCHECK(!will_delete)
        << "Task runners must have been stopped by this stage of shutdown.";
  }
}

const WebRtcLogFileInfo& WebRtcEventLogUploaderImpl::GetWebRtcLogFileInfo()
    const {
  DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
  return log_file_;
}

bool WebRtcEventLogUploaderImpl::Cancel() {
  DCHECK(io_task_runner_->RunsTasksInCurrentSequence());

  // The upload could already have been completed, or maybe was never properly
  // started (due to a file read failure, etc.).
  const bool upload_was_active = (url_loader_.get() != nullptr);

  // Note that in this case, it might still be that the last bytes hit the
  // wire right as we attempt to cancel the upload. OnURLFetchComplete, however,
  // will not be called.
  url_loader_.reset();

  DeleteLogFile();
  DeleteHistoryFile();

  if (upload_was_active) {
    UmaRecordWebRtcEventLoggingUpload(
        WebRtcEventLoggingUploadUma::kUploadCancelled);
  }

  return upload_was_active;
}

bool WebRtcEventLogUploaderImpl::PrepareUploadData(std::string* upload_data) {
  DCHECK(io_task_runner_->RunsTasksInCurrentSequence());

  std::string log_file_contents;
  if (!base::ReadFileToStringWithMaxSize(log_file_.path, &log_file_contents,
                                         max_log_file_size_bytes_)) {
    LOG(WARNING) << "Couldn't read event log file, or max file size exceeded.";
    UmaRecordWebRtcEventLoggingUpload(
        WebRtcEventLoggingUploadUma::kLogFileReadError);
    return false;
  }

  DCHECK(upload_data->empty());
  upload_data->reserve(log_file_contents.size() + kExpectedMimeOverheadBytes);

  const std::string filename_str = log_file_.path.BaseName().MaybeAsASCII();
  if (filename_str.empty()) {
    LOG(WARNING) << "Log filename is not according to acceptable format.";
    UmaRecordWebRtcEventLoggingUpload(
        WebRtcEventLoggingUploadUma::kLogFileNameError);
    return false;
  }

  const char* filename = filename_str.c_str();

  net::AddMultipartValueForUpload("prod", kProduct, kBoundary, std::string(),
                                  upload_data);
  net::AddMultipartValueForUpload("ver",
                                  version_info::GetVersionNumber() + "-webrtc",
                                  kBoundary, std::string(), upload_data);
  net::AddMultipartValueForUpload("guid", "0", kBoundary, std::string(),
                                  upload_data);
  net::AddMultipartValueForUpload("type", filename, kBoundary, std::string(),
                                  upload_data);
  AddFileContents(filename, log_file_contents, "application/log", upload_data);
  net::AddMultipartFinalDelimiterForUpload(kBoundary, upload_data);

  return true;
}

void WebRtcEventLogUploaderImpl::StartUpload(const std::string& upload_data) {
  DCHECK(io_task_runner_->RunsTasksInCurrentSequence());

  auto resource_request = std::make_unique<network::ResourceRequest>();
  resource_request->url = GURL(kUploadURL);
  resource_request->method = "POST";
  resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;

  // Create a new mojo pipe. It's safe to pass this around and use
  // immediately, even though it needs to finish initialization on the UI
  // thread.
  network::mojom::URLLoaderFactoryPtr url_loader_factory_ptr;
  base::PostTask(FROM_HERE, {content::BrowserThread::UI},
                 base::BindOnce(BindURLLoaderFactoryReceiver,
                                mojo::MakeRequest(&url_loader_factory_ptr)));

  url_loader_ = network::SimpleURLLoader::Create(
      std::move(resource_request), kWebrtcEventLogUploaderTrafficAnnotation);
  url_loader_->AttachStringForUpload(upload_data, MimeContentType());
  url_loader_->SetOnUploadProgressCallback(
      base::BindRepeating(OnURLLoadUploadProgress));

  // See comment in destructor for an explanation about why using
  // base::Unretained(this) is safe here.
  url_loader_->DownloadToString(
      url_loader_factory_ptr.get(),
      base::BindOnce(&WebRtcEventLogUploaderImpl::OnURLLoadComplete,
                     base::Unretained(this)),
      kWebRtcEventLogMaxUploadIdBytes);
}

void WebRtcEventLogUploaderImpl::OnURLLoadComplete(
    std::unique_ptr<std::string> response_body) {
  DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
  DCHECK(url_loader_);

  if (response_body.get() != nullptr && response_body->empty()) {
    LOG(WARNING) << "SimpleURLLoader reported upload successful, "
                 << "but report ID unknown.";
  }

  const bool upload_successful =
      (response_body.get() != nullptr && !response_body->empty());

  // NetError() is 0 when no error occurred.
  UmaRecordWebRtcEventLoggingNetErrorType(url_loader_->NetError());

  DCHECK(history_file_writer_);
  if (upload_successful) {
    if (!history_file_writer_->WriteUploadId(*response_body)) {
      // Discard the incomplete, potentially now corrupt history file, but the
      // upload is still considered successful.
      LOG(ERROR) << "Failed to write upload ID to history file.";
      DeleteHistoryFile();
    }
  } else {
    LOG(WARNING) << "Upload unsuccessful.";
    // By not writing an UploadId to the history file, it is inferrable that
    // the upload was initiated, but did not end successfully.
  }

  UmaRecordWebRtcEventLoggingUpload(
      upload_successful ? WebRtcEventLoggingUploadUma::kSuccess
                        : WebRtcEventLoggingUploadUma::kUploadFailure);

  url_loader_.reset();  // Explicitly maintain determinant.

  ReportResult(upload_successful);
}

void WebRtcEventLogUploaderImpl::ReportResult(bool result) {
  DCHECK(io_task_runner_->RunsTasksInCurrentSequence());

  // * If the upload was successful, the file is no longer needed.
  // * If the upload failed, we don't want to retry, because we run the risk of
  //   uploading significant amounts of data once again, only for the upload to
  //   fail again after (as an example) wasting 50MBs of upload bandwidth.
  // * If the file was not found, this will simply have no effect (other than
  //   to LOG() an error).
  // TODO(crbug.com/775415): Provide refined retrial behavior.
  DeleteLogFile();

  // Release hold of history file, allowing it to be read, moved or deleted.
  history_file_writer_.reset();

  io_task_runner_->PostTask(
      FROM_HERE, base::BindOnce(std::move(callback_), log_file_.path, result));
}

void WebRtcEventLogUploaderImpl::DeleteLogFile() {
  DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
  const bool deletion_successful =
      base::DeleteFile(log_file_.path, /*recursive=*/false);
  if (!deletion_successful) {
    // This is a somewhat serious (though unlikely) error, because now we'll
    // try to upload this file again next time Chrome launches.
    LOG(ERROR) << "Could not delete pending WebRTC event log file.";
  }
}

void WebRtcEventLogUploaderImpl::DeleteHistoryFile() {
  DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
  if (!history_file_writer_) {
    LOG(ERROR) << "Deletion of history file attempted after uploader "
               << "has relinquished ownership of it.";
    return;
  }
  history_file_writer_->Delete();
  history_file_writer_.reset();
}

}  // namespace webrtc_event_logging