// 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.

#ifndef CONTENT_BROWSER_CODE_CACHE_GENERATED_CODE_CACHE_H_
#define CONTENT_BROWSER_CODE_CACHE_GENERATED_CODE_CACHE_H_

#include <queue>

#include "base/containers/queue.h"
#include "base/files/file_path.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "content/common/content_export.h"
#include "net/base/completion_callback.h"
#include "net/base/io_buffer.h"
#include "net/disk_cache/disk_cache.h"
#include "url/origin.h"

namespace content {

// Cache for storing generated code from the renderer on the disk. This cache
// uses |resource_url| + |origin_lock| as a key for storing the generated code.
// |resource_url| is the url corresponding to the requested resource.
// |origin_lock| is the origin that the renderer which requested this resource
// is locked to. This is used to enforce site isolation policy on cached code.
// For example, if SitePerProcess is enabled and http://script.com/script1.js is
// requested by http://example.com, then http://script.com/script.js is the
// resource_url and http://example.com is the origin_lock.
//
// The key is generated by concatenating the serialized url and origin lock
// with a separator in between. The separator is non-valid URL characters, to
// prevent any attacks by crafting the URLs. |origin_lock| could be empty when
// renderer is not locked to an origin (ex:SitePerProcess is disabled) and it
// is safe to use only |resource_url| as the key in such cases.
//
// This uses a simple disk_cache backend. It just stores one data stream and
// stores response_time + generated code as one data blob.
//
// There exists one cache per storage partition and is owned by the storage
// partition. This cache is created, accessed and destroyed on the I/O
// thread.
class CONTENT_EXPORT GeneratedCodeCache {
 public:
  using ReadDataCallback =
      base::RepeatingCallback<void(const base::Time&,
                                   const std::vector<uint8_t>&)>;
  static const int kResponseTimeSizeInBytes = sizeof(int64_t);

  // Cache type. Used for collecting statistics for JS and Wasm in separate
  // buckets.
  enum CodeCacheType { kJavaScript, kWebAssembly };

  // Used for collecting statistics about cache behaviour.
  enum CacheEntryStatus {
    kHit,
    kMiss,
    kClear,
    kUpdate,
    kCreate,
    kError,
    kIncompleteEntry,
    kMaxValue = kIncompleteEntry
  };

  // Creates a GeneratedCodeCache with the specified path and the maximum size.
  // If |max_size_bytes| is 0, then disk_cache picks a default size based on
  // some heuristics.
  GeneratedCodeCache(const base::FilePath& path,
                     int max_size_bytes,
                     CodeCacheType cache_type);

  ~GeneratedCodeCache();

  // Writes data to the cache. If there is an entry corresponding to
  // <|resource_url|, |origin_lock|> this overwrites the existing data. If
  // there is no entry it creates a new one.
  void WriteData(const GURL& resource_url,
                 const GURL& origin_lock,
                 const base::Time& response_time,
                 const std::vector<uint8_t>& data);

  // Fetch entry corresponding to <resource_url, origin_lock> from the cache
  // and return it using the ReadDataCallback.
  void FetchEntry(const GURL& resource_url,
                  const GURL& origin_lock,
                  ReadDataCallback);

  // Delete the entry corresponding to <resource_url, origin_lock>
  void DeleteEntry(const GURL& resource_url, const GURL& origin_lock);

  // Clear code cache.
  // TODO(mythria): Add support to conditional clearing based on URL
  // and time range.
  // TODO(mythria): Also check if we can avoid retruning an error code and
  // always call the callback to be consistent with other methods.
  int ClearCache(net::CompletionCallback callback);

  const base::FilePath& path() const { return path_; }

 private:
  class PendingOperation;
  using ScopedBackendPtr = std::unique_ptr<disk_cache::Backend>;

  // State of the backend.
  enum BackendState { kUnInitialized, kInitializing, kInitialized, kFailed };

  // The operation requested.
  enum Operation { kFetch, kWrite, kDelete, kClearCache };

  // Data streams corresponding to each entry.
  enum { kDataIndex = 1 };

  // Creates a simple_disk_cache backend.
  void CreateBackend();
  void DidCreateBackend(
      scoped_refptr<base::RefCountedData<ScopedBackendPtr>> backend_ptr,
      int rv);

  // The requests that are received while tha backend is being initialized
  // are recorded in pending operations list. This function issues all pending
  // operations.
  void IssuePendingOperations();

  // Write entry to cache
  void WriteDataImpl(const std::string& key,
                     scoped_refptr<net::IOBufferWithSize> buffer);
  void CompleteForWriteData(
      scoped_refptr<net::IOBufferWithSize> buffer,
      const std::string& key,
      scoped_refptr<base::RefCountedData<disk_cache::EntryWithOpened>>
          entry_struct,
      int rv);
  void WriteDataCompleted(const std::string& key, int rv);

  // Fetch entry from cache
  void FetchEntryImpl(const std::string& key, ReadDataCallback);
  void OpenCompleteForReadData(
      ReadDataCallback callback,
      const std::string& key,
      scoped_refptr<base::RefCountedData<disk_cache::Entry*>> entry,
      int rv);
  void ReadDataComplete(const std::string& key,
                        ReadDataCallback callback,
                        scoped_refptr<net::IOBufferWithSize> buffer,
                        int rv);

  // Delete entry from cache
  void DeleteEntryImpl(const std::string& key);

  // Issues the queued operation at the front of the queue for the given |key|.
  void IssueQueuedOperationForEntry(const std::string& key);
  // Enqueues into the list if there is an in-progress operation. Otherwise
  // creates an entry to indicate there is an active operation.
  bool EnqueueAsPendingOperation(const std::string& key,
                                 std::unique_ptr<PendingOperation> op);
  void IssueOperation(PendingOperation* op);

  void DoPendingClearCache(net::CompletionCallback callback);
  void PendingClearComplete(net::CompletionCallback callback, int rv);

  void CollectStatistics(GeneratedCodeCache::CacheEntryStatus status);

  std::unique_ptr<disk_cache::Backend> backend_;
  BackendState backend_state_;

  std::vector<std::unique_ptr<PendingOperation>> pending_ops_;

  // Map from key to queue ops.
  std::map<std::string, base::queue<std::unique_ptr<PendingOperation>>>
      active_entries_map_;

  base::FilePath path_;
  int max_size_bytes_;
  CodeCacheType cache_type_;

  base::WeakPtrFactory<GeneratedCodeCache> weak_ptr_factory_;

  DISALLOW_COPY_AND_ASSIGN(GeneratedCodeCache);
};

}  // namespace content

#endif  // CONTENT_BROWSER_CODE_CACHE_GENERATED_CODE_CACHE_H_
