blob: 6295f075b0eb8894b41f9e8eaf0e8671c664baf0 [file]
// 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_