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

#ifndef NET_HTTP_CACHE_BODY_COMPRESSOR_H_
#define NET_HTTP_CACHE_BODY_COMPRESSOR_H_

#include <cstdint>
#include <memory>
#include <optional>

#include "base/containers/span.h"
#include "base/memory/scoped_refptr.h"
#include "net/base/net_export.h"

#if !defined(NET_DISABLE_ZSTD_COMPRESS)
// Forward-declare the zstd compression context to avoid pulling zstd.h into
// this header. The full definition is only needed in the .cc file.
struct ZSTD_CCtx_s;
using ZSTD_CCtx = ZSTD_CCtx_s;
#endif

namespace net {

class IOBuffer;

// Encapsulates zstd streaming compression for writing cache entry bodies.
//
// This class owns all zstd compression state (ZSTD_CCtx, output buffer, byte
// counters) and exposes a minimal interface for HttpCache::Writers to call at
// well-defined points in its state machine. All zstd-specific logic is
// confined here, keeping the Writers state machine clean.
//
// Usage (see HttpCache::Writers::DoCacheWriteData /
// DoCacheWriteDataComplete for the real wiring):
//   compressor_ = std::make_unique<CacheBodyCompressor>();
//   if (!compressor_->Init()) { /* fall back to uncompressed */ }
//   ...
//   int out = compressor_->Compress(input, /*is_last_chunk=*/false);
//   if (out > 0)
//     entry->WriteData(..., compressor_->output_buffer(), out, ...);
//   ...
//   // At EOF:
//   int remaining = compressor_->Finalize();
//   // Write compressor_->output_buffer() with compressor_->output_length(),
//   // then repeat until Finalize() returns 0.
//
// Thread-safety: Not thread-safe. Must be used on a single sequence.
class NET_EXPORT_PRIVATE CacheBodyCompressor {
 public:
  CacheBodyCompressor();
  ~CacheBodyCompressor();

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

  // Initializes the zstd compression context with level 1 (fastest setting
  // that still gives useful compression). Returns true on success, false if
  // initialization fails. Must be called before Compress() or Finalize().
  bool Init();

  // Compresses `input` bytes into the internal output buffer. If
  // `is_last_chunk` is true, the zstd frame is also finalized in the same
  // call (equivalent to calling Finalize() inline after the last Compress()).
  //
  // Returns:
  //   > 0: number of compressed bytes available in output_buffer()
  //     0: zstd buffered the input internally and produced no output yet
  //   < 0: compression error (ERR_CACHE_COMPRESSION_FAILURE)
  //
  // After a successful call with return > 0, use output_buffer() and
  // output_length() to retrieve the bytes to write to disk.
  int Compress(base::span<const uint8_t> input, bool is_last_chunk);

  // Flushes any remaining compressed data by ending the zstd frame. May need
  // to be called multiple times if the output buffer fills before the frame
  // epilogue is fully emitted.
  //
  // Returns:
  //   >= 0: number of bytes that zstd still needs to flush. When 0,
  //         finalization is complete.
  //   < 0:  compression error (ERR_CACHE_COMPRESSION_FAILURE).
  //
  // After each successful call, output_length() contains the bytes the
  // caller should write to disk.
  int Finalize();

  // Returns the output buffer containing compressed data from the most recent
  // Compress() or Finalize() call.
  //
  // The returned pointer is valid until the next call to Compress(),
  // Finalize(), or Reset(). The caller must copy or write the data before
  // making another call.
  //
  // Truncation contract: if the network stream is interrupted mid-body, the
  // caller MUST doom the cache entry — a partial zstd frame is undecodable.
  // Do NOT store a truncated compressed body.
  IOBuffer* output_buffer() const { return output_buffer_.get(); }

  // Returns the number of compressed bytes produced by the most recent
  // Compress() or Finalize() call.
  size_t output_length() const { return output_length_; }

  // Returns total uncompressed input bytes processed across all Compress()
  // calls. Used by the caller to populate
  // HttpResponseInfo::zstd_uncompressed_body_size, which the read path uses
  // as the over-decompression bound. (Note: this is intentionally the count
  // of plaintext bytes streamed into zstd, NOT the wire Content-Length —
  // see HttpResponseInfo for why those are not the same thing.)
  int64_t total_input_bytes() const { return total_input_bytes_; }

  // Resets per-stream state so the same instance can be reused. The zstd
  // context is reset (not freed) to avoid re-allocation on the next Init().
  // Safe to call even if Init() was never called.
  void Reset();

  // Causes Compress() to fail with ERR_CACHE_COMPRESSION_FAILURE once
  // `total_input_bytes_` exceeds `max_size`. Used by tests to exercise
  // error paths in HttpCache::Writers that are otherwise unreachable.
  void set_max_uncompressed_size_for_testing(int64_t max_size) {
    max_uncompressed_size_for_testing_ = max_size;
  }

 private:
#if !defined(NET_DISABLE_ZSTD_COMPRESS)
  struct ZstdCCtxDeleter {
    void operator()(ZSTD_CCtx* ctx) const;
  };

  // Grows output_buffer_ so it has room for at least `needed` more bytes.
  // Preserves the first `bytes_already_written` bytes. Growth is rounded up
  // to at least buffer_size/16 to prevent slow linear resizing.
  void GrowOutputBufferSizeByAtLeast(size_t needed,
                                     size_t bytes_already_written);

  std::unique_ptr<ZSTD_CCtx, ZstdCCtxDeleter> cctx_;
#endif

  scoped_refptr<IOBuffer> output_buffer_;
  size_t output_length_ = 0;
  int64_t total_input_bytes_ = 0;
  std::optional<int64_t> max_uncompressed_size_for_testing_;
};

}  // namespace net

#endif  // NET_HTTP_CACHE_BODY_COMPRESSOR_H_
