chromium/chrome/browser/ash/policy/reporting/single_install_event_log.h

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

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#ifndef CHROME_BROWSER_ASH_POLICY_REPORTING_SINGLE_INSTALL_EVENT_LOG_H_
#define CHROME_BROWSER_ASH_POLICY_REPORTING_SINGLE_INSTALL_EVENT_LOG_H_

#include <stddef.h>
#include <stdint.h>
#include <deque>
#include <memory>
#include <string>
#include "base/containers/heap_array.h"
#include "base/files/file.h"

namespace policy {

// An event log for install process of single app. App refers to extension or
// ARC++ app. The log can be stored on disk and serialized for upload to a
// server. The log is internally held in a round-robin buffer. An |incomplete_|
// flag indicates whether any log entries were lost (e.g. entry too large or
// buffer wrapped around). Log entries are pruned and the flag is cleared after
// upload has completed. |T| specifies the event type.
template <typename T>
class SingleInstallEventLog {
 public:
  explicit SingleInstallEventLog(const std::string& id);
  ~SingleInstallEventLog();

  const std::string& id() const { return id_; }

  int size() const { return events_.size(); }

  bool empty() const { return events_.empty(); }

  // Add a log entry. If the buffer is full, the oldest entry is removed and
  // |incomplete_| is set.
  void Add(const T& event);

  // Stores the event log to |file|. Returns |true| if the log was written
  // successfully in a self-delimiting manner and the file may be used to store
  // further logs.
  bool Store(base::File* file) const;

  // Clears log entries that were previously serialized. Also clears
  // |incomplete_| if all log entries added since serialization are still
  // present in the log.
  void ClearSerialized();

  static const int kLogCapacity = 1024;
  static const int kMaxBufferSize = 65536;

 protected:
  // Tries to parse the app name. Returns true if parsing app name is
  // successful.
  static bool ParseIdFromFile(base::File* file,
                              ssize_t* size,
                              base::HeapArray<char>* package_buffer);

  // Restores the event log from |file| into |log|. Returns |true| if the
  // self-delimiting format of the log was parsed successfully and further logs
  // stored in the file may be loaded.
  static bool LoadEventLogFromFile(base::File* file,
                                   SingleInstallEventLog<T>* log);

  // The app this event log pertains to.
  const std::string id_;

  // The buffer holding log entries.
  std::deque<T> events_;

  // Whether any log entries were lost (e.g. entry too large or buffer wrapped
  // around).
  bool incomplete_ = false;

  // The number of entries that were serialized and can be cleared from the log
  // after successful upload to the server, or -1 if none.
  int serialized_entries_ = -1;
};

// Implementation details below here.
template <typename T>
const int SingleInstallEventLog<T>::kLogCapacity;
template <typename T>
const int SingleInstallEventLog<T>::kMaxBufferSize;

template <typename T>
SingleInstallEventLog<T>::SingleInstallEventLog(const std::string& id)
    : id_(id) {}

template <typename T>
SingleInstallEventLog<T>::~SingleInstallEventLog() {}

template <typename T>
void SingleInstallEventLog<T>::Add(const T& event) {
  events_.push_back(event);
  if (events_.size() > kLogCapacity) {
    incomplete_ = true;
    if (serialized_entries_ > -1) {
      --serialized_entries_;
    }
    events_.pop_front();
  }
}

template <typename T>
bool SingleInstallEventLog<T>::Store(base::File* file) const {
  if (!file->IsValid()) {
    return false;
  }

  ssize_t size = id_.size();
  if (file->WriteAtCurrentPos(reinterpret_cast<const char*>(&size),
                              sizeof(size)) != sizeof(size)) {
    return false;
  }

  if (file->WriteAtCurrentPos(id_.data(), size) != size) {
    return false;
  }

  const int64_t incomplete = incomplete_;
  if (file->WriteAtCurrentPos(reinterpret_cast<const char*>(&incomplete),
                              sizeof(incomplete)) != sizeof(incomplete)) {
    return false;
  }

  const ssize_t entries = events_.size();
  if (file->WriteAtCurrentPos(reinterpret_cast<const char*>(&entries),
                              sizeof(entries)) != sizeof(entries)) {
    return false;
  }

  for (const T& event : events_) {
    size = event.ByteSizeLong();
    base::HeapArray<char> buffer;

    if (size > kMaxBufferSize) {
      // Log entry too large. Skip it.
      size = 0;
    } else {
      buffer = base::HeapArray<char>::Uninit(size);
      if (!event.SerializeToArray(buffer.data(), size)) {
        // Log entry serialization failed. Skip it.
        size = 0;
      }
    }

    if (file->WriteAtCurrentPos(reinterpret_cast<const char*>(&size),
                                sizeof(size)) != sizeof(size) ||
        (size && file->WriteAtCurrentPos(buffer.data(), size) != size)) {
      return false;
    }
  }

  return true;
}

template <typename T>
void SingleInstallEventLog<T>::ClearSerialized() {
  if (serialized_entries_ == -1) {
    return;
  }

  events_.erase(events_.begin(), events_.begin() + serialized_entries_);
  serialized_entries_ = -1;
  incomplete_ = false;
}

template <typename T>
bool SingleInstallEventLog<T>::ParseIdFromFile(
    base::File* file,
    ssize_t* size,
    base::HeapArray<char>* package_buffer) {
  if (!file->IsValid())
    return false;
  if (file->ReadAtCurrentPos(reinterpret_cast<char*>(size), sizeof(*size)) !=
          sizeof(*size) ||
      *size < 0 || *size > kMaxBufferSize) {
    return false;
  }
  *package_buffer = base::HeapArray<char>::Uninit(*size);

  if (file->ReadAtCurrentPos((*package_buffer).data(), *size) != *size) {
    return false;
  }
  return true;
}

template <typename T>
bool SingleInstallEventLog<T>::LoadEventLogFromFile(
    base::File* file,
    SingleInstallEventLog<T>* log) {
  int64_t incomplete;
  if (file->ReadAtCurrentPos(reinterpret_cast<char*>(&incomplete),
                             sizeof(incomplete)) != sizeof(incomplete)) {
    return false;
  }
  log->incomplete_ = incomplete;
  ssize_t entries;
  if (file->ReadAtCurrentPos(reinterpret_cast<char*>(&entries),
                             sizeof(entries)) != sizeof(entries)) {
    return false;
  }
  for (ssize_t i = 0; i < entries; ++i) {
    ssize_t size;
    if (file->ReadAtCurrentPos(reinterpret_cast<char*>(&size), sizeof(size)) !=
            sizeof(size) ||
        size < 0 || size > kMaxBufferSize) {
      log->incomplete_ = true;
      return false;
    }

    if (size == 0) {
      // Zero-size entries are written if serialization of a log entry fails.
      // Skip these on read.
      log->incomplete_ = true;
      continue;
    }

    auto buffer = base::HeapArray<char>::Uninit(size);
    if (file->ReadAtCurrentPos(buffer.data(), size) != size) {
      log->incomplete_ = true;
      return false;
    }

    T event;
    if (event.ParseFromArray(buffer.data(), size)) {
      log->Add(event);
    } else {
      log->incomplete_ = true;
    }
  }

  return true;
}

}  // namespace policy

#endif  // CHROME_BROWSER_ASH_POLICY_REPORTING_SINGLE_INSTALL_EVENT_LOG_H_