/* compression_utils_portable.cc
 *
 * Copyright 2019 The Chromium Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the Chromium source repository LICENSE file.
 */

#include "compression_utils_portable.h"

#include <stddef.h>
#include <stdlib.h>
#include <string.h>

// WebKit-specific change: the WinCairo port seems to have a system
// zlib.h, but doesn't define z_const.
#if !defined(z_const)
#    define z_const
#endif

namespace zlib_internal
{

// The difference in bytes between a zlib header and a gzip header.
const size_t kGzipZlibHeaderDifferenceBytes = 16;

// Pass an integer greater than the following get a gzip header instead of a
// zlib header when calling deflateInit2() and inflateInit2().
const int kWindowBitsToGetGzipHeader = 16;

// This describes the amount of memory zlib uses to compress data. It can go
// from 1 to 9, with 8 being the default. For details, see:
// http://www.zlib.net/manual.html (search for memLevel).
const int kZlibMemoryLevel = 8;

// The expected compressed size is based on the input size factored by
// internal Zlib constants (e.g. window size, etc) plus the wrapper
// header size.
uLongf GzipExpectedCompressedSize(uLongf input_size)
{
    return kGzipZlibHeaderDifferenceBytes + compressBound(input_size);
}

// The expected decompressed size is stored in the last
// 4 bytes of |input| in LE. See https://tools.ietf.org/html/rfc1952#page-5
uint32_t GetGzipUncompressedSize(const Bytef *compressed_data, size_t length)
{
    uint32_t size;
    if (length < sizeof(size))
        return 0;

    memcpy(&size, &compressed_data[length - sizeof(size)], sizeof(size));
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
    return size;
#else
    return __builtin_bswap32(size);
#endif
}

// The number of window bits determines the type of wrapper to use - see
// https://cs.chromium.org/chromium/src/third_party/zlib/zlib.h?l=566
inline int ZlibStreamWrapperType(WrapperType type)
{
    if (type == ZLIB)  // zlib DEFLATE stream wrapper
        return MAX_WBITS;
    if (type == GZIP)  // gzip DEFLATE stream wrapper
        return MAX_WBITS + kWindowBitsToGetGzipHeader;
    if (type == ZRAW)  // no wrapper, use raw DEFLATE
        return -MAX_WBITS;
    return 0;
}

int GzipCompressHelper(Bytef *dest,
                       uLongf *dest_length,
                       const Bytef *source,
                       uLong source_length,
                       void *(*malloc_fn)(size_t),
                       void (*free_fn)(void *))
{
    return CompressHelper(GZIP, dest, dest_length, source, source_length, Z_DEFAULT_COMPRESSION,
                          malloc_fn, free_fn);
}

// This code is taken almost verbatim from third_party/zlib/compress.c. The only
// difference is deflateInit2() is called which allows different window bits to
// be set. > 16 causes a gzip header to be emitted rather than a zlib header,
// and negative causes no header to emitted.
//
// Compression level can be a number from 1-9, with 1 being the fastest, 9 being
// the best compression. The default, which the GZIP helper uses, is 6.
int CompressHelper(WrapperType wrapper_type,
                   Bytef *dest,
                   uLongf *dest_length,
                   const Bytef *source,
                   uLong source_length,
                   int compression_level,
                   void *(*malloc_fn)(size_t),
                   void (*free_fn)(void *))
{
    if (compression_level < 0 || compression_level > 9)
    {
        compression_level = Z_DEFAULT_COMPRESSION;
    }

    z_stream stream;

    // FIXME(cavalcantii): z_const is not defined as 'const'.
    stream.next_in   = static_cast<z_const Bytef *>(const_cast<Bytef *>(source));
    stream.avail_in  = static_cast<uInt>(source_length);
    stream.next_out  = dest;
    stream.avail_out = static_cast<uInt>(*dest_length);
    if (static_cast<uLong>(stream.avail_out) != *dest_length)
        return Z_BUF_ERROR;

    // Cannot convert capturing lambdas to function pointers directly, hence the
    // structure.
    struct MallocFreeFunctions
    {
        void *(*malloc_fn)(size_t);
        void (*free_fn)(void *);
    } malloc_free = {malloc_fn, free_fn};

    if (malloc_fn)
    {
        if (!free_fn)
            return Z_BUF_ERROR;

        auto zalloc = [](void *opaque, uInt items, uInt size) {
            return reinterpret_cast<MallocFreeFunctions *>(opaque)->malloc_fn(items * size);
        };
        auto zfree = [](void *opaque, void *address) {
            return reinterpret_cast<MallocFreeFunctions *>(opaque)->free_fn(address);
        };

        stream.zalloc = static_cast<alloc_func>(zalloc);
        stream.zfree  = static_cast<free_func>(zfree);
        stream.opaque = static_cast<voidpf>(&malloc_free);
    }
    else
    {
        stream.zalloc = static_cast<alloc_func>(0);
        stream.zfree  = static_cast<free_func>(0);
        stream.opaque = static_cast<voidpf>(0);
    }

    int err =
        deflateInit2(&stream, compression_level, Z_DEFLATED, ZlibStreamWrapperType(wrapper_type),
                     kZlibMemoryLevel, Z_DEFAULT_STRATEGY);
    if (err != Z_OK)
        return err;

    // This has to exist outside of the if statement to prevent it going off the
    // stack before deflate(), which will use this object.
    gz_header gzip_header;
    if (wrapper_type == GZIP)
    {
        memset(&gzip_header, 0, sizeof(gzip_header));
        err = deflateSetHeader(&stream, &gzip_header);
        if (err != Z_OK)
            return err;
    }

    err = deflate(&stream, Z_FINISH);
    if (err != Z_STREAM_END)
    {
        deflateEnd(&stream);
        return err == Z_OK ? Z_BUF_ERROR : err;
    }
    *dest_length = stream.total_out;

    err = deflateEnd(&stream);
    return err;
}

int GzipUncompressHelper(Bytef *dest, uLongf *dest_length, const Bytef *source, uLong source_length)
{
    return UncompressHelper(GZIP, dest, dest_length, source, source_length);
}

// This code is taken almost verbatim from third_party/zlib/uncompr.c. The only
// difference is inflateInit2() is called which allows different window bits to
// be set. > 16 causes a gzip header to be emitted rather than a zlib header,
// and negative causes no header to emitted.
int UncompressHelper(WrapperType wrapper_type,
                     Bytef *dest,
                     uLongf *dest_length,
                     const Bytef *source,
                     uLong source_length)
{
    z_stream stream;

    // FIXME(cavalcantii): z_const is not defined as 'const'.
    stream.next_in  = static_cast<z_const Bytef *>(const_cast<Bytef *>(source));
    stream.avail_in = static_cast<uInt>(source_length);
    if (static_cast<uLong>(stream.avail_in) != source_length)
        return Z_BUF_ERROR;

    stream.next_out  = dest;
    stream.avail_out = static_cast<uInt>(*dest_length);
    if (static_cast<uLong>(stream.avail_out) != *dest_length)
        return Z_BUF_ERROR;

    stream.zalloc = static_cast<alloc_func>(0);
    stream.zfree  = static_cast<free_func>(0);

    int err = inflateInit2(&stream, ZlibStreamWrapperType(wrapper_type));
    if (err != Z_OK)
        return err;

    err = inflate(&stream, Z_FINISH);
    if (err != Z_STREAM_END)
    {
        inflateEnd(&stream);
        if (err == Z_NEED_DICT || (err == Z_BUF_ERROR && stream.avail_in == 0))
            return Z_DATA_ERROR;
        return err;
    }
    *dest_length = stream.total_out;

    err = inflateEnd(&stream);
    return err;
}

}  // namespace zlib_internal
