chromium/chrome/browser/ash/policy/dlp/dlp_files_event_storage.cc

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ash/policy/dlp/dlp_files_event_storage.h"

#include "base/containers/flat_map.h"
#include "base/metrics/histogram_functions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "chrome/browser/chromeos/policy/dlp/dlp_file_destination.h"
#include "chrome/browser/chromeos/policy/dlp/dlp_rules_manager.h"
#include "components/enterprise/data_controls/core/browser/dlp_histogram_helper.h"

namespace policy {

DlpFilesEventStorage::DlpFilesEventStorage(base::TimeDelta cooldown_timeout,
                                           size_t entries_num_limit)
    : cooldown_delta_(cooldown_timeout),
      task_runner_(base::SequencedTaskRunner::GetCurrentDefault()),
      entries_num_limit_(entries_num_limit) {}
DlpFilesEventStorage::~DlpFilesEventStorage() = default;

DlpFilesEventStorage::EventEntry::EventEntry(base::TimeTicks timestamp)
    : timestamp(timestamp) {}
DlpFilesEventStorage::EventEntry::~EventEntry() = default;

bool DlpFilesEventStorage::StoreEventAndCheckIfItShouldBeReported(
    FileId file_id,
    const DlpFileDestination& dst) {
  if (entries_num_ == entries_num_limit_) {
    // If we end up here we probably have already spammed the server with a lot
    // of events, better to stop for a while.
    return false;
  }

  const base::TimeTicks now = base::TimeTicks::Now();

  const auto file_it = events_.find(file_id);
  if (file_it == events_.end()) {  // Check for new (file_id, dst) pair
    InsertNewFileAndDestinationPair(file_id, dst, now);
    return true;
  }

  auto dst_it = file_it->second.find(dst);
  if (dst_it == file_it->second.end()) {  // Check for new dst for this file_id
    AddDestinationToFile(file_it, file_id, dst, now);
    // Skip reporting if we don't know the destination (i.e., it is
    // kUnknownComponent) and at least an entry for `file_id` is stored in
    // `events_`.
    return (dst.component().has_value() &&
            dst.component().value() !=
                data_controls::Component::kUnknownComponent) ||
           dst.url().has_value();
  }

  // Found existing (file_id, dst) pair, update it
  UpdateFileAndDestinationPair(dst_it, now);

  const auto time_diff = now - dst_it->second.timestamp;

  // Record the time difference between two identical file events.
  base::UmaHistogramTimes(data_controls::GetDlpHistogramPrefix() +
                              data_controls::dlp::kSameFileEventTimeDiffUMA,
                          time_diff);

  // Report only if enough time has passed.
  return time_diff > cooldown_delta_;
}

base::TimeDelta DlpFilesEventStorage::GetDeduplicationCooldownForTesting()
    const {
  return cooldown_delta_;
}

std::size_t DlpFilesEventStorage::GetSizeForTesting() const {
  return entries_num_;
}

void DlpFilesEventStorage::SetTaskRunnerForTesting(
    scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
  task_runner_ = task_runner;
}

void DlpFilesEventStorage::AddDestinationToFile(
    EventsMap::iterator file_it,
    FileId file_id,
    const DlpFileDestination& dst,
    const base::TimeTicks timestamp) {
  const auto [it, _] = file_it->second.emplace(dst, timestamp);
  StartEvictionTimer(file_id, dst, it->second);
  entries_num_++;
  data_controls::DlpCountHistogram(data_controls::dlp::kActiveFileEventsCount,
                                   entries_num_, entries_num_limit_);
}

void DlpFilesEventStorage::InsertNewFileAndDestinationPair(
    FileId file_id,
    const DlpFileDestination& dst,
    const base::TimeTicks timestamp) {
  const auto [file_it, _] =
      events_.emplace(file_id, std::map<DlpFileDestination, EventEntry>());
  const auto [dst_it, __] = file_it->second.emplace(dst, timestamp);
  StartEvictionTimer(file_id, dst, dst_it->second);
  entries_num_++;
  data_controls::DlpCountHistogram(data_controls::dlp::kActiveFileEventsCount,
                                   entries_num_, entries_num_limit_);
}

void DlpFilesEventStorage::UpdateFileAndDestinationPair(
    DestinationsMap::iterator dst_it,
    const base::TimeTicks timestamp) {
  dst_it->second.timestamp = timestamp;
  DCHECK(dst_it->second.eviction_timer.IsRunning());
  dst_it->second.eviction_timer.Reset();
  data_controls::DlpCountHistogram(data_controls::dlp::kActiveFileEventsCount,
                                   entries_num_, entries_num_limit_);
}

void DlpFilesEventStorage::StartEvictionTimer(FileId file_id,
                                              const DlpFileDestination& dst,
                                              EventEntry& event_value) {
  event_value.eviction_timer.SetTaskRunner(task_runner_);
  event_value.eviction_timer.Start(
      FROM_HERE, cooldown_delta_,
      base::BindOnce(&DlpFilesEventStorage::OnEvictionTimerUp,
                     base::Unretained(this), file_id, dst));
}

void DlpFilesEventStorage::OnEvictionTimerUp(FileId file_id,
                                             DlpFileDestination dst) {
  auto event_it = events_.find(file_id);
  DCHECK(event_it != events_.end());
  DCHECK(event_it->second.count(dst));
  event_it->second.erase(std::move(dst));
  if (event_it->second.empty()) {
    events_.erase(event_it);
  }
  --entries_num_;
}

}  // namespace policy