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

#ifndef STORAGE_BROWSER_BLOB_BLOB_READER_H_
#define STORAGE_BROWSER_BLOB_BLOB_READER_H_

#include <stddef.h>
#include <stdint.h>

#include <map>
#include <memory>
#include <optional>
#include <vector>

#include "base/component_export.h"
#include "base/functional/callback_forward.h"
#include "base/gtest_prod_util.h"
#include "base/memory/weak_ptr.h"
#include "base/sequence_checker.h"
#include "base/types/expected.h"
#include "mojo/public/cpp/base/big_buffer.h"
#include "mojo/public/cpp/system/data_pipe.h"
#include "net/base/completion_once_callback.h"
#include "net/base/net_errors.h"
#include "services/network/public/cpp/data_pipe_to_source_stream.h"
#include "storage/browser/blob/blob_storage_constants.h"

class GURL;

namespace base {
class FilePath;
class TaskRunner;
class Time;
}

namespace net {
class DrainableIOBuffer;
class IOBuffer;
}

namespace storage {
class BlobDataItem;
class BlobDataHandle;
class BlobDataSnapshot;
class FileStreamReader;

// The blob reader is used to read a blob.  This can only be used in the browser
// process, and we need to be on the IO thread.
//  * There can only be one read happening at a time per reader.
//  * If a status of Status::NET_ERROR is returned, that means there was an
//    error and the net_error() variable contains the error code.
// Use a BlobDataHandle to create an instance.
//
// For more information on how to read Blobs in your specific situation, see:
// https://chromium.googlesource.com/chromium/src/+/HEAD/storage/browser/blob/README.md#how-to-use-blobs-browser_side-accessing-reading
class COMPONENT_EXPORT(STORAGE_BROWSER) BlobReader {
 public:
  class COMPONENT_EXPORT(STORAGE_BROWSER) FileStreamReaderProvider {
   public:
    virtual ~FileStreamReaderProvider();

    virtual std::unique_ptr<FileStreamReader> CreateForLocalFile(
        base::TaskRunner* task_runner,
        const base::FilePath& file_path,
        int64_t initial_offset,
        const base::Time& expected_modification_time) = 0;

    virtual std::unique_ptr<FileStreamReader> CreateFileStreamReader(
        const GURL& filesystem_url,
        int64_t offset,
        int64_t max_bytes_to_read,
        const base::Time& expected_modification_time) = 0;
  };
  enum class Status { NET_ERROR, IO_PENDING, DONE };
  using StatusCallback = base::OnceCallback<void(Status)>;

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

  virtual ~BlobReader();

  // This calculates the total size of the blob, and initializes the reading
  // cursor.
  //  * This should only be called once per reader.
  //  * Status::Done means that the total_size() value is populated and you can
  //    continue to SetReadRange or Read.
  //  * The 'done' callback is only called if Status::IO_PENDING is returned.
  //    The callback value contains the error code or net::OK. Please use the
  //    total_size() value to query the blob size, as it's uint64_t.
  Status CalculateSize(net::CompletionOnceCallback done);

  // Returns true when the blob has side data. CalculateSize must be called
  // beforehand. Currently side data is supported only for single readable
  // DataHandle entry blob. So it returns false when the blob has more than
  // single data item. This side data is used to pass the V8 code cache which is
  // stored as a side stream in the CacheStorage to the renderer.
  // (crbug.com/581613).  This will still return true even after TakeSideData
  // has been called.
  bool has_side_data() const;

  // Reads the side data of the blob. CalculateSize must be called beforehand.
  // * Always calls the StatusCallback when the side data has been read.
  // * This may be done synchronously or asynchronously.
  // * The done callback will be called with Status::DONE or Status::NET_ERROR.
  // * If the callback returns NET_ERROR, net_error() will have the value.
  // Currently side data is supported only for single readable DataHandle entry
  // blob.
  void ReadSideData(StatusCallback done);

  // Passes the side data (if any) from ReadSideData() to the caller.
  std::optional<mojo_base::BigBuffer> TakeSideData();

  // Used to set the read position.
  // * This should be called after CalculateSize and before Read.
  // * Range can only be set once.
  Status SetReadRange(uint64_t position, uint64_t length);

  // Reads a portion of the data.
  // * `CalculateSize()` (and optionally `SetReadRange()`) must be called
  //   beforehand.
  // * `bytes_read` is populated only if `Status::DONE` is returned. Otherwise
  //   the bytes read (or error code) is populated in the `done` callback.
  // * The `done` callback is only called if `Status::IO_PENDING` is returned.
  // * This method can be called multiple times. A bytes read value (either from
  //   the callback for `Status::IO_PENDING` or the `bytes_read` value for
  //   `Status::DONE`) of 0 means we're finished reading.
  // TODO (crbug.com/362658602): Use separate parameters for the error code and
  // bytes read in `done`.
  Status Read(net::IOBuffer* buffer,
              size_t dest_size,
              int* bytes_read,
              net::CompletionOnceCallback done);

  // Returns true iff this reader contains a single MojoDataItem. When true,
  // `ReadSingleMojoDataItem()` can be called instead of multiple `Read()`s as
  // an optimized path. This can only be called after `CalculateSize()`.
  bool IsSingleMojoDataItem() const;

  // Reads the data from the single MojoDataItem directly into `producer` and
  // passes the `net::Error` from the operation to the `done` callback.
  void ReadSingleMojoDataItem(mojo::ScopedDataPipeProducerHandle producer,
                              net::CompletionOnceCallback done);

  // Kills reading and invalidates all callbacks. The reader cannot be used
  // after this call.
  void Kill();

  // Returns if all of the blob's items are in memory. Should only be called
  // after CalculateSize.
  bool IsInMemory() const;

  // Returns the remaining bytes to be read in the blob. This is populated
  // after CalculateSize, and is modified by SetReadRange.
  uint64_t remaining_bytes() const {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    return remaining_bytes_;
  }

  // Returns the net error code if there was an error. Defaults to `net::OK`.
  // Not set when `ReadSingleMojoDataItem()` is used.
  // TODO (crbug.com/362658602): Make this an `std::optional` instead.
  int net_error() const {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    return net_error_;
  }

  // Returns the total size of the blob. This is populated after CalculateSize
  // is called.
  uint64_t total_size() const {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    DCHECK(total_size_calculated_);
    return total_size_;
  }

 protected:
  friend class BlobDataHandle;
  friend class BlobReaderTest;
  FRIEND_TEST_ALL_PREFIXES(BlobReaderTest, HandleBeforeAsyncCancel);
  FRIEND_TEST_ALL_PREFIXES(BlobReaderTest, ReadFromIncompleteBlob);

  explicit BlobReader(const BlobDataHandle* blob_handle);

  bool total_size_calculated() const {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    return total_size_calculated_;
  }

  void SetFileStreamProviderForTesting(
      std::unique_ptr<FileStreamReaderProvider> file_stream_provider) {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    file_stream_provider_for_testing_ = std::move(file_stream_provider);
  }

 private:
  Status ReportError(int net_error);
  void InvalidateCallbacksAndDone(int net_error,
                                  net::CompletionOnceCallback done);

  void AsyncCalculateSize(net::CompletionOnceCallback done, BlobStatus status);
  // If this returns Status::IO_PENDING, it will take ownership of the callback
  // pointed at by |done| and call it when complete. |done| will not be
  // modified, otherwise.
  Status CalculateSizeImpl(net::CompletionOnceCallback* done);
  bool AddItemLength(size_t index, uint64_t length);
  bool ResolveFileItemLength(const BlobDataItem& item,
                             int64_t total_length,
                             uint64_t* output_length);
  void DidGetFileItemLength(size_t index,
                            base::expected<int64_t, net::Error> result);
  void DidCountSize();

  // For reading the blob.
  // Returns if we're done, PENDING_IO if we're waiting on async.
  Status ReadLoop(int* bytes_read);
  // Called from asynchronously called methods to continue the read loop.
  void ContinueAsyncReadLoop();
  // PENDING_IO means we're waiting on async.
  Status ReadItem();
  void AdvanceItem();
  void AdvanceBytesRead(int result);
  void ReadBytesItem(const BlobDataItem& item, int bytes_to_read);
  BlobReader::Status ReadFileItem(FileStreamReader* reader, int bytes_to_read);
  void DidReadFile(int result);
  void DeleteItemReaders();
  Status ReadReadableDataHandle(const BlobDataItem& item, int bytes_to_read);
  void DidReadReadableDataHandle(int result);
  void DidReadItem(int result);
  void DidReadSideData(StatusCallback done,
                       int expected_size,
                       int result,
                       mojo_base::BigBuffer data);
  int ComputeBytesToRead() const;
  int BytesReadCompleted();

  // Returns a FileStreamReader for a blob item at |index|.
  // If the item at |index| is not of type kFile this returns nullptr.
  FileStreamReader* GetOrCreateFileReaderAtIndex(size_t index);
  // If the reader is null, then this basically performs a delete operation.
  void SetFileReaderAtIndex(size_t index,
                            std::unique_ptr<FileStreamReader> reader);
  // Creates a FileStreamReader for the item with additional_offset.
  std::unique_ptr<FileStreamReader> CreateFileStreamReader(
      const BlobDataItem& item,
      uint64_t additional_offset);
  // Returns a DataPipeToSourceStream for a blob item at |Index|.
  // If the item at |index| is not of type kReadableDataHandle this returns
  // nullptr.
  network::DataPipeToSourceStream* GetOrCreateDataPipeAtIndex(size_t index);
  void SetDataPipeAtIndex(
      size_t index,
      std::unique_ptr<network::DataPipeToSourceStream> pipe);
  std::unique_ptr<network::DataPipeToSourceStream> CreateDataPipe(
      const BlobDataItem& item,
      uint64_t additional_offset);

  void RecordBytesReadFromDataHandle(int item_index, int result);

  std::unique_ptr<BlobDataHandle> blob_handle_;
  std::unique_ptr<BlobDataSnapshot> blob_data_;
  std::unique_ptr<FileStreamReaderProvider> file_stream_provider_for_testing_;
  scoped_refptr<base::TaskRunner> file_task_runner_;
  std::optional<mojo_base::BigBuffer> side_data_;

  int net_error_;
  bool item_list_populated_ = false;
  std::vector<uint64_t> item_length_list_;

  scoped_refptr<net::DrainableIOBuffer> read_buf_;

  bool total_size_calculated_ = false;
  uint64_t total_size_ = 0;
  uint64_t remaining_bytes_ = 0;
  size_t pending_get_file_info_count_ = 0;
  std::map<size_t, std::unique_ptr<FileStreamReader>> index_to_reader_;
  std::map<size_t, std::unique_ptr<network::DataPipeToSourceStream>>
      index_to_pipe_;
  size_t current_item_index_ = 0;
  uint64_t current_item_offset_ = 0;

  bool io_pending_ = false;

  net::CompletionOnceCallback size_callback_;
  net::CompletionOnceCallback read_callback_;

  SEQUENCE_CHECKER(sequence_checker_);

  base::WeakPtrFactory<BlobReader> weak_factory_{this};
};

}  // namespace storage
#endif  // STORAGE_BROWSER_BLOB_BLOB_READER_H_
