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

#ifndef COMPONENTS_NACL_BROWSER_NACL_BROWSER_H_
#define COMPONENTS_NACL_BROWSER_NACL_BROWSER_H_

#include <stdint.h>

#include <memory>

#include "base/containers/circular_deque.h"
#include "base/containers/lru_cache.h"
#include "base/files/file.h"
#include "base/functional/bind.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "components/nacl/browser/nacl_browser_delegate.h"
#include "components/nacl/browser/nacl_validation_cache.h"

namespace base {
class FileProxy;
}

namespace nacl {

static const int kGdbDebugStubPortUnknown = -1;
static const int kGdbDebugStubPortUnused = 0;

// Keep the cache bounded to an arbitrary size.  If it's too small, useful
// entries could be evicted when multiple .nexes are loaded at once.  On the
// other hand, entries are not always claimed (and hence removed), so the size
// of the cache will likely saturate at its maximum size.
// Entries may not be claimed for two main reasons. 1) the NaCl process could
// be killed while it is loading.  2) the trusted NaCl plugin opens files using
// the code path but doesn't resolve them.
// TODO(ncbray) don't cache files that the plugin will not resolve.
static const int kFilePathCacheSize = 100;

// Open an immutable executable file that can be mmapped (or a read-only file).
// This function should only be called on a thread that can perform file IO.
base::File OpenNaClReadExecImpl(const base::FilePath& file_path,
                                bool is_executable);

// Represents shared state for all NaClProcessHost objects in the browser.
class NaClBrowser {
 public:
  static NaClBrowser* GetInstance();

  NaClBrowser(const NaClBrowser&) = delete;
  NaClBrowser& operator=(const NaClBrowser&) = delete;

  // Will it be possible to launch a NaCl process, eventually?
  bool IsOk() const;

  // Are we ready to launch a NaCl process now?  Implies IsOk().
  bool IsReady() const;

  // Attempt to asynchronously acquire all resources needed to start a process.
  // This method is idempotent - it is safe to call multiple times.
  void EnsureAllResourcesAvailable();

  // Enqueues reply() in the message loop when all the resources needed to start
  // a process have been acquired.
  void WaitForResources(base::OnceClosure reply);

  // Asynchronously attempt to get the IRT open.
  // This is entailed by EnsureInitialized.  This method is exposed as part of
  // the public interface, however, so the IRT can be explicitly opened as
  // early as possible to prevent autoupdate issues.
  void EnsureIrtAvailable();

  // Path to IRT. Available even before IRT is loaded.
  const base::FilePath& GetIrtFilePath();

  // IRT file handle, only available when IsReady().
  const base::File& IrtFile() const;

  // Methods for tracking the GDB debug stub port associated with each NaCl
  // process.
  void SetProcessGdbDebugStubPort(int process_id, int port);
  int GetProcessGdbDebugStubPort(int process_id);

  // While a test has a GDB debug port callback set, Chrome will allocate a
  // currently-unused TCP port to the debug stub server, instead of a fixed
  // one.
  static void SetGdbDebugStubPortListenerForTest(
      base::RepeatingCallback<void(int)> listener);
  static void ClearGdbDebugStubPortListenerForTest();

  enum ValidationCacheStatus {
    CACHE_MISS = 0,
    CACHE_HIT,
    CACHE_MAX
  };

  bool ValidationCacheIsEnabled() const {
    return validation_cache_is_enabled_;
  }

  const std::string& GetValidationCacheKey() const {
    return validation_cache_.GetValidationCacheKey();
  }

  // The instance keeps information about NaCl executable files opened via
  // PPAPI.  This allows the NaCl process to get trusted information about the
  // file directly from the browser process.  In theory, a compromised renderer
  // could provide a writable file handle or lie about the file's path.  If we
  // trusted the handle was read only but it was not, an mmapped file could be
  // modified after validation, allowing an escape from the NaCl sandbox.
  // Similarly, if we trusted the file path corresponded to the file handle but
  // it did not, the validation cache could be tricked into bypassing validation
  // for bad code.
  // Instead of allowing these attacks, the NaCl process only trusts information
  // it gets directly from the browser process.  Because the information is
  // stored in a cache of bounded size, it is not guaranteed the browser process
  // will be able to provide the requested information.  In these cases, the
  // NaCl process must make conservative assumptions about the origin of the
  // file.
  // In theory, a compromised renderer could guess file tokens in an attempt to
  // read files it normally doesn't have access to.  This would not compromise
  // the NaCl sandbox, however, and only has a 1 in ~2**120 chance of success
  // per guess.
  // TODO(ncbray): move the cache onto NaClProcessHost so that we don't need to
  // rely on tokens being unguessable by another process.
  void PutFilePath(const base::FilePath& path,
                   uint64_t* file_token_lo,
                   uint64_t* file_token_hi);
  bool GetFilePath(uint64_t file_token_lo,
                   uint64_t file_token_hi,
                   base::FilePath* path);

  bool QueryKnownToValidate(const std::string& signature, bool off_the_record);
  void SetKnownToValidate(const std::string& signature, bool off_the_record);
  void ClearValidationCache(base::OnceClosure callback);

  void EarlyStartup();

  // Set/get the NaClBrowserDelegate. The |delegate| must be set at startup,
  // from the Browser's UI thread. It will be leaked at browser teardown.
  static void SetDelegate(std::unique_ptr<NaClBrowserDelegate> delegate);
  static NaClBrowserDelegate* GetDelegate();
  static void ClearAndDeleteDelegate();

  // Called whenever a NaCl process exits.
  void OnProcessEnd(int process_id);

  // Called whenever a NaCl process crashes, before OnProcessEnd().
  void OnProcessCrashed();

  // If "too many" crashes occur within a given time period, NaCl is throttled
  // until the rate again drops below the threshold.
  bool IsThrottled();

 private:
  enum NaClResourceState {
    NaClResourceUninitialized,
    NaClResourceRequested,
    NaClResourceReady
  };

  static NaClBrowser* GetInstanceInternal();

  NaClBrowser();
  ~NaClBrowser();

  void InitIrtFilePath();

  void OpenIrtLibraryFile();

  void OnIrtOpened(std::unique_ptr<base::FileProxy> file_proxy,
                   base::File::Error error_code);

  void InitValidationCacheFilePath();
  void EnsureValidationCacheAvailable();
  void OnValidationCacheLoaded(const std::string* data);
  void RunWithoutValidationCache();

  // Dispatch waiting tasks if we are ready, or if we know we'll never be ready.
  void CheckWaiting();

  // Indicate that it is impossible to launch a NaCl process.
  void MarkAsFailed();

  void MarkValidationCacheAsModified();
  void PersistValidationCache();

  base::File irt_file_;
  base::FilePath irt_filepath_;
  NaClResourceState irt_state_ = NaClResourceUninitialized;
  NaClValidationCache validation_cache_;
  NaClValidationCache off_the_record_validation_cache_;
  base::FilePath validation_cache_file_path_;
  bool validation_cache_is_enabled_ = false;
  bool validation_cache_is_modified_ = false;
  NaClResourceState validation_cache_state_ = NaClResourceUninitialized;
  base::RepeatingCallback<void(int)> debug_stub_port_listener_;

  // Map from process id to debug stub port if any.
  typedef std::map<int, int> GdbDebugStubPortMap;
  GdbDebugStubPortMap gdb_debug_stub_port_map_;

  typedef base::HashingLRUCache<std::string, base::FilePath> PathCacheType;
  PathCacheType path_cache_{kFilePathCacheSize};

  // True if it is no longer possible to launch NaCl processes.
  bool has_failed_ = false;

  // A list of pending tasks to start NaCl processes.
  std::vector<base::OnceClosure> waiting_;

  base::circular_deque<base::Time> crash_times_;

  scoped_refptr<base::SequencedTaskRunner> file_task_runner_ =
      base::ThreadPool::CreateSequencedTaskRunner(
          {base::MayBlock(), base::TaskPriority::USER_VISIBLE});
};

} // namespace nacl

#endif  // COMPONENTS_NACL_BROWSER_NACL_BROWSER_H_
