// SPDX-License-Identifier: LGPL-2.1
/*
 * Copyright (C) 2021, VMware, Tzvetomir Stoyanov tz.stoyanov@gmail.com>
 *
 */
#include <stdlib.h>
#include <sys/time.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>

#include "trace-cmd-private.h"
#include "trace-cmd-local.h"

struct compress_proto {
	struct compress_proto *next;
	char *proto_name;
	char *proto_version;
	int weight;

	int (*compress_block)(void *ctx, const void *in, int in_bytes, void *out, int out_bytes);
	int (*uncompress_block)(void *ctx, const void *in,  int in_bytes, void *out, int out_bytes);
	unsigned int (*compress_size)(void *ctx, unsigned int bytes);
	bool (*is_supported)(const char *name, const char *version);
	void *(*new_context)(void);
	void (*free_context)(void *ctx);
};

static struct compress_proto *proto_list;

struct tracecmd_compression {
	int				fd;
	unsigned int			capacity;
	unsigned int			capacity_read;
	unsigned int			pointer;
	char				*buffer;
	struct compress_proto		*proto;
	struct tep_handle		*tep;
	struct tracecmd_msg_handle	*msg_handle;
	void				*context;
};

static int read_fd(int fd, char *dst, int len)
{
	size_t size = 0;
	int r;

	do {
		r = read(fd, dst+size, len);
		if (r > 0) {
			size += r;
			len -= r;
		} else
			break;
	} while (r > 0);

	if (len)
		return -1;
	return size;
}

static long long write_fd(int fd, const void *data, size_t size)
{
	long long tot = 0;
	long long w;

	do {
		w = write(fd, data + tot, size - tot);
		tot += w;

		if (!w)
			break;
		if (w < 0)
			return w;
	} while (tot != size);

	return tot;
}

static long long do_write(struct tracecmd_compression *handle,
			  const void *data, unsigned long long size)
{
	int ret;

	if (handle->msg_handle) {
		ret = tracecmd_msg_data_send(handle->msg_handle, data, size);
		if (ret)
			return -1;
		return size;
	}
	return write_fd(handle->fd, data, size);
}

static inline int buffer_extend(struct tracecmd_compression *handle, unsigned int size)
{
	int extend;
	char *buf;

	if (size <= handle->capacity)
		return 0;

	extend = (size / BUFSIZ + 1) * BUFSIZ;
	buf = realloc(handle->buffer, extend);
	if (!buf)
		return -1;
	handle->buffer = buf;
	handle->capacity = extend;

	return 0;
}

/**
 * tracecmd_compress_lseek - Move the read/write pointer into the compression buffer
 * @handle: compression handle
 * @offset: number of bytes to move the pointer, can be negative or positive
 * @whence: the starting position of the pointer movement,
 *
 * Returns the new file pointer on success, or -1 in case of an error.
 */
off_t tracecmd_compress_lseek(struct tracecmd_compression *handle, off_t offset, int whence)
{
	unsigned long p;

	if (!handle || !handle->buffer)
		return (off_t)-1;

	switch (whence) {
	case SEEK_CUR:
		p = handle->pointer + offset;
		break;
	case SEEK_END:
		p = handle->capacity + offset;
		break;
	case SEEK_SET:
		p = offset;
		break;
	default:
		return (off_t)-1;
	}

	if (buffer_extend(handle, p))
		return (off_t)-1;

	handle->pointer = p;

	return p;
}

static int compress_read(struct tracecmd_compression *handle, char *dst, int len)
{

	if (handle->pointer > handle->capacity_read)
		return -1;

	if (handle->pointer + len > handle->capacity_read)
		len = handle->capacity_read - handle->pointer;

	memcpy(dst, handle->buffer + handle->pointer, len);

	return len;
}

/**
 * tracecmd_compress_pread - pread() on compression buffer
 * @handle: compression handle
 * @dst: return, store the read data
 * @len: length of data to be read
 * @offset: offset in the buffer of data to be read
 *
 * Read a @len of data from the compression buffer at given @offset,
 * without updating the buffer pointer.
 *
 * On success returns the number of bytes read, or -1 on failure.
 */
int tracecmd_compress_pread(struct tracecmd_compression *handle, char *dst, int len, off_t offset)
{
	int ret;

	if (!handle || !handle->buffer || offset > handle->capacity_read)
		return -1;

	ret = tracecmd_compress_lseek(handle, offset, SEEK_SET);
	if (ret < 0)
		return ret;
	return compress_read(handle, dst, len);
}

/**
 * tracecmd_compress_buffer_read - read() from compression buffer
 * @handle: compression handle
 * @dst: return, store the read data
 * @len: length of data to be read
 *
 * Read a @len of data from the compression buffer
 *
 * On success returns the number of bytes read, or -1 on failure.
 */
int tracecmd_compress_buffer_read(struct tracecmd_compression *handle, char *dst, int len)
{
	int ret;

	if (!handle || !handle->buffer)
		return -1;

	ret = compress_read(handle, dst, len);
	if (ret > 0)
		handle->pointer += ret;

	return ret;
}

/**
 * tracecmd_compress_reset - Reset the compression buffer
 * @handle: compression handle
 *
 * Reset the compression buffer, any data currently in the buffer
 * will be destroyed.
 *
 */
void tracecmd_compress_reset(struct tracecmd_compression *handle)
{
	if (!handle)
		return;

	free(handle->buffer);
	handle->buffer = NULL;
	handle->pointer = 0;
	handle->capacity_read = 0;
	handle->capacity = 0;
}

/**
 * tracecmd_uncompress_block - uncompress a memory block
 * @handle: compression handle
 *
 * Read compressed memory block from the file and uncompress it into
 * internal buffer. The tracecmd_compress_buffer_read() can be used
 * to read the uncompressed data from the buffer.
 *
 * Returns 0 on success, or -1 in case of an error.
 */
int tracecmd_uncompress_block(struct tracecmd_compression *handle)
{
	unsigned int s_uncompressed;
	unsigned int s_compressed;
	char *bytes = NULL;
	char buf[4];
	int size;
	int ret;

	if (!handle || !handle->proto || !handle->proto->uncompress_block)
		return -1;

	tracecmd_compress_reset(handle);

	if (read(handle->fd, buf, 4) != 4)
		return -1;

	s_compressed = tep_read_number(handle->tep, buf, 4);
	if (read(handle->fd, buf, 4) != 4)
		return -1;

	s_uncompressed = tep_read_number(handle->tep, buf, 4);
	size = s_uncompressed > s_compressed ? s_uncompressed : s_compressed;

	handle->buffer = malloc(size);
	if (!handle->buffer)
		return -1;

	bytes = malloc(s_compressed);
	if (!bytes)
		goto error;

	if (read_fd(handle->fd, bytes, s_compressed) < 0)
		goto error;

	ret = handle->proto->uncompress_block(handle->context,
					      bytes, s_compressed, handle->buffer, size);
	if (ret < 0)
		goto error;

	free(bytes);
	handle->pointer = 0;
	handle->capacity_read = ret;
	handle->capacity = size;
	return 0;
error:
	tracecmd_compress_reset(handle);
	free(bytes);
	return -1;
}

/**
 * tracecmd_compress_block - compress a memory block
 * @handle: compression handle
 *
 * Compress the content of the internal memory buffer and write
 * the compressed data in the file. The tracecmd_compress_buffer_write()
 * can be used to write data into the internal memory buffer,
 * before calling this API.
 *
 * Returns 0 on success, or -1 in case of an error.
 */
int tracecmd_compress_block(struct tracecmd_compression *handle)
{
	unsigned int size, real_size;
	char *buf;
	int endian4;
	int ret;

	if (!handle || !handle->proto ||
	    !handle->proto->compress_size || !handle->proto->compress_block)
		return -1;

	size = handle->proto->compress_size(handle->context, handle->pointer);

	buf = malloc(size);
	if (!buf)
		return -1;

	real_size = handle->proto->compress_block(handle->context, handle->buffer, handle->pointer, buf, size);
	if (real_size < 0) {
		ret = real_size;
		goto out;
	}

	/* Write compressed data size */
	endian4 = tep_read_number(handle->tep, &real_size, 4);
	ret = do_write(handle, &endian4, 4);
	if (ret != 4)
		goto out;

	/* Write uncompressed data size */
	endian4 = tep_read_number(handle->tep, &handle->pointer, 4);
	ret = do_write(handle, &endian4, 4);
	if (ret != 4) {
		ret = -1;
		goto out;
	}

	/* Write compressed data */
	ret = do_write(handle, buf, real_size);
	if (ret != real_size) {
		ret = -1;
		goto out;
	}

	ret = 0;
	tracecmd_compress_reset(handle);
out:
	free(buf);
	return ret;
}

/**
 * tracecmd_compress_buffer_write - write() to compression buffer
 * @handle: compression handle
 * @data: data to be written
 * @size: size of @data
 *
 * Write @data of @size in the compression buffer
 *
 * Returns 0 on success, or -1 on failure.
 */
int tracecmd_compress_buffer_write(struct tracecmd_compression *handle,
				   const void *data, unsigned long long size)
{
	if (!handle)
		return -1;

	if (buffer_extend(handle, handle->pointer + size))
		return -1;

	memcpy(&handle->buffer[handle->pointer], data, size);
	handle->pointer += size;
	if (handle->capacity_read < handle->pointer)
		handle->capacity_read = handle->pointer;

	return 0;
}

/**
 * tracecmd_compress_init - initialize the library with available compression algorithms
 */
void tracecmd_compress_init(void)
{
	struct timeval time;

	gettimeofday(&time, NULL);
	srand((time.tv_sec * 1000) + (time.tv_usec / 1000));

#ifdef HAVE_ZLIB
	tracecmd_zlib_init();
#endif
	tracecmd_zstd_init();
}

static struct compress_proto *compress_proto_select(void)
{
	struct compress_proto *proto = proto_list;
	struct compress_proto *selected = NULL;

	while (proto) {
		if (!selected || selected->weight > proto->weight)
			selected = proto;
		proto = proto->next;
	}

	return selected;
}

/**
 * tracecmd_compress_alloc - Allocate a new compression context
 * @name: name of the compression algorithm.
 *        If NULL - auto select the best available algorithm
 * @version: version of the compression algorithm, can be NULL
 * @fd: file descriptor for reading / writing data
 * @tep: tep handle, used to encode the data
 * @msg_handle: message handle, use it for reading / writing data instead of @fd
 *
 * Returns NULL on failure or pointer to allocated compression context.
 * The returned context must be freed by tracecmd_compress_destroy()
 */
struct tracecmd_compression *tracecmd_compress_alloc(const char *name, const char *version,
						     int fd, struct tep_handle *tep,
						     struct tracecmd_msg_handle *msg_handle)
{
	struct tracecmd_compression *new;
	struct compress_proto *proto;

	if (name) {
		proto = proto_list;
		while (proto) {
			if (proto->is_supported && proto->is_supported(name, version))
				break;
			proto = proto->next;
		}
	} else {
		proto = compress_proto_select();
	}
	if (!proto)
		return NULL;

	new = calloc(1, sizeof(*new));
	if (!new)
		return NULL;

	new->fd = fd;
	new->tep = tep;
	new->msg_handle = msg_handle;
	new->proto = proto;
	if (proto->new_context)
		new->context = proto->new_context();

	return new;
}

/**
 * tracecmd_compress_destroy - Free a compression context
 * @handle: handle to the compression context that will be freed
 */
void tracecmd_compress_destroy(struct tracecmd_compression *handle)
{
	if (!handle)
		return;

	tracecmd_compress_reset(handle);

	if (handle->proto && handle->proto->free_context)
		handle->proto->free_context(handle->context);

	free(handle);
}

/**
 * tracecmd_compress_is_supported - check if compression algorithm is supported
 * @name: name of the compression algorithm.
 * @version: version of the compression algorithm.
 *
 * Checks if compression algorithm with given name and version is supported.
 * Returns true if the algorithm is supported or false if it is not.
 */
bool tracecmd_compress_is_supported(const char *name, const char *version)
{
	struct compress_proto *proto = proto_list;

	if (!name)
		return NULL;

	while (proto) {
		if (proto->is_supported && proto->is_supported(name, version))
			return true;
		proto = proto->next;
	}
	return false;
}

/**
 * tracecmd_compress_proto_get_name - get name and version of compression algorithm
 * @compress: compression handle.
 * @name: return, name of the compression algorithm.
 * @version: return, version of the compression algorithm.
 *
 * Returns 0 on success, or -1 in case of an error. If 0 is returned, the name
 * and version of the algorithm are stored in @name and @version. The returned
 * strings must *not* be freed.
 */
int tracecmd_compress_proto_get_name(struct tracecmd_compression *compress,
				     const char **name, const char **version)
{
	if (!compress || !compress->proto)
		return -1;

	if (name)
		*name = compress->proto->proto_name;
	if (version)
		*version = compress->proto->proto_version;

	return 0;
}

/**
 * tracecmd_compress_proto_register - register a new compression algorithm
 * @name: name of the compression algorithm.
 * @version: version of the compression algorithm.
 * @weight: weight of the compression algorithm, lower is better.
 * @compress: compression hook, called to compress a memory block.
 * @uncompress: uncompression hook, called to uncompress a memory block.
 * @compress_size: hook, called to get the required minimum size of the buffer
 *		   for compression given number of bytes.
 * @is_supported: check hook, called to check if compression with given name and
 *		   version is supported by this plugin.
 *
 * Returns 0 on success, or -1 in case of an error. If algorithm with given name
 * and version is already registered, -1 is returned.
 */
int tracecmd_compress_proto_register(struct tracecmd_compression_proto *proto)
{
	struct compress_proto *new;

	if (!proto || !proto->name || !proto->compress || !proto->uncompress)
		return -1;

	if (tracecmd_compress_is_supported(proto->name, proto->version))
		return -1;

	new = calloc(1, sizeof(*new));
	if (!new)
		return -1;

	new->proto_name = strdup(proto->name);
	if (!new->proto_name)
		goto error;

	new->proto_version = strdup(proto->version);
	if (!new->proto_version)
		goto error;

	new->compress_block = proto->compress;
	new->uncompress_block = proto->uncompress;
	new->compress_size = proto->compress_size;
	new->is_supported = proto->is_supported;
	new->weight = proto->weight;
	new->next = proto_list;
	new->new_context = proto->new_context;
	new->free_context = proto->free_context;
	proto_list = new;
	return 0;

error:
	free(new->proto_name);
	free(new->proto_version);
	free(new);
	return -1;
}

/**
 * tracecmd_compress_free - free the library resources, related to available compression algorithms
 *
 */
void tracecmd_compress_free(void)
{
	struct compress_proto *proto = proto_list;
	struct compress_proto *del;

	while (proto) {
		del = proto;
		proto = proto->next;
		free(del->proto_name);
		free(del->proto_version);
		free(del);
	}
	proto_list = NULL;
}

/**
 * tracecmd_compress_protos_get - get a list of all supported compression algorithms and versions
 * @names: return, array with names of all supported compression algorithms
 * @versions: return, array with versions of all supported compression algorithms
 *
 * On success, the size of @names and @versions arrays is returned.
 * Those arrays are allocated by the API and must be freed with free() by the
 * caller. Both arrays are with same size, each name from @names corresponds to
 * a version from @versions. The last element in both arrays is a NULL pointer.
 * On error -1 is returned and @names and @versions arrays are not allocated.
 */
int tracecmd_compress_protos_get(char ***names, char ***versions)
{
	struct compress_proto *proto = proto_list;
	char **n = NULL;
	char **v = NULL;
	int c, i;

	for (c = 0; proto; proto = proto->next)
		c++;

	if (c < 1)
		return c;

	n = calloc(c + 1, sizeof(char *));
	if (!n)
		goto error;
	v = calloc(c + 1, sizeof(char *));
	if (!v)
		goto error;

	proto = proto_list;
	for (i = 0; i < c && proto; i++) {
		n[i] = proto->proto_name;
		v[i] = proto->proto_version;
		proto = proto->next;
	}

	n[i] = NULL;
	v[i] = NULL;
	*names = n;
	*versions = v;
	return c;

error:
	free(n);
	free(v);
	return -1;
}

/**
 * tracecmd_compress_copy_from - Copy and compress data from a file
 * @handle: compression handle
 * @fd: file descriptor to uncompressed data to copy from
 * @chunk_size: size of one compression chunk
 * @read_size: Pointer to max bytes to read from. The pointer is updated
 *	       with the actual size of compressed data read. If 0 is passed,
 *	       read until the EOF is reached.
 * @write_size: return, size of the compressed data written into @handle
 *
 * This function reads uncompressed data from given @fd, compresses the data
 * using the @handle compression context and writes the compressed data into the
 * fd associated with the @handle. The data is compressed on chunks with given
 * @chunk_size size. The compressed data is written in the format:
 *  - 4 bytes, chunks count
 *  - for each chunk:
 *    - 4 bytes, size of compressed data in this chunk
 *    - 4 bytes, uncompressed size of the data in this chunk
 *    - data, bytes of <size of compressed data in this chunk>
 *
 * On success 0 is returned, @read_size and @write_size are updated with the size of
 * read and written data.
 */
int tracecmd_compress_copy_from(struct tracecmd_compression *handle, int fd, int chunk_size,
				unsigned long long *read_size, unsigned long long *write_size)
{
	unsigned int rchunk = 0;
	unsigned int chunks = 0;
	unsigned int rsize = 0;
	unsigned int rmax = 0;
	unsigned int csize;
	unsigned int size;
	unsigned int all;
	unsigned int r;
	off_t end_offset;
	off_t offset;
	char *buf_from;
	char *buf_to;
	int endian4;
	int ret;

	if (!handle || !handle->proto ||
	    !handle->proto->compress_block || !handle->proto->compress_size)
		return 0;

	if (read_size)
		rmax = *read_size;
	csize = handle->proto->compress_size(handle->context, chunk_size);
	buf_from = malloc(chunk_size);
	if (!buf_from)
		return -1;

	buf_to = malloc(csize);
	if (!buf_to) {
		free(buf_from);
		return -1;
	}

	/* save the initial offset and write 0 as initial chunk count */
	offset = lseek(handle->fd, 0, SEEK_CUR);
	write_fd(handle->fd, &chunks, 4);

	do {
		all = 0;
		if (rmax > 0 && (rmax - rsize) < chunk_size)
			rchunk = (rmax - rsize);
		else
			rchunk = chunk_size;

		do {
			r = read(fd, buf_from + all, rchunk - all);
			all += r;

			if (r <= 0)
				break;
		} while (all != rchunk);


		if (r < 0 || (rmax > 0 && rsize >= rmax))
			break;
		rsize += all;
		size = csize;
		if (all > 0) {
			ret = handle->proto->compress_block(handle->context,
							    buf_from, all, buf_to, size);
			if (ret < 0) {
				if (errno == EINTR)
					continue;
				break;
			}
			size = ret;
			/* Write compressed data size */
			endian4 = tep_read_number(handle->tep, &size, 4);
			ret = write_fd(handle->fd, &endian4, 4);
			if (ret != 4)
				break;

			/* Write uncompressed data size */
			endian4 = tep_read_number(handle->tep, &all, 4);
			ret = write_fd(handle->fd, &endian4, 4);
			if (ret != 4)
				break;

			/* Write the compressed data */
			ret = write_fd(handle->fd, buf_to, size);
			if (ret != size)
				break;
			chunks++;
		}
	} while (all > 0);

	free(buf_from);
	free(buf_to);

	if (all)
		return -1;

	if (lseek(handle->fd, offset, SEEK_SET) == (off_t)-1)
		return -1;

	endian4 = tep_read_number(handle->tep, &chunks, 4);
	/* write chunks count*/
	write_fd(handle->fd, &chunks, 4);
	end_offset = lseek(handle->fd, 0, SEEK_END);
	if (end_offset == (off_t)-1)
		return -1;

	if (read_size)
		*read_size = rsize;
	if (write_size)
		*write_size = end_offset - offset;
	return 0;
}

/**
 * tracecmd_load_chunks_info - Read compression chunks information from the file
 * @handle: compression handle
 * @chunks_info: return, array with compression chunks information
 *
 * This function reads information of all compression chunks in the current
 * compression block from the file and fills that information in a newly
 * allocated array @chunks_info which is returned.
 *
 * On success count of compression chunks is returned. Array of that count is
 * allocated and returned in @chunks_info. Each entry describes one compression
 * chunk. On error -1 is returned. In case of success, @chunks_info must be
 * freed by free().
 */
int tracecmd_load_chunks_info(struct tracecmd_compression *handle,
			      struct tracecmd_compress_chunk **chunks_info)
{
	struct tracecmd_compress_chunk *chunks = NULL;
	unsigned long long size = 0;
	unsigned int count = 0;
	off_t offset;
	int ret = -1;
	char buf[4];
	int i;

	if (!handle)
		return -1;

	offset = lseek(handle->fd, 0, SEEK_CUR);
	if (offset == (off_t)-1)
		return -1;

	if (read(handle->fd, buf, 4) != 4)
		return -1;

	count = tep_read_number(handle->tep, buf, 4);
	if (!count) {
		ret = 0;
		goto out;
	}

	chunks = calloc(count, sizeof(struct tracecmd_compress_chunk));
	if (!chunks)
		goto out;

	for (i = 0; i < count; i++) {
		chunks[i].zoffset = lseek(handle->fd, 0, SEEK_CUR);
		if (chunks[i].zoffset == (off_t)-1)
			goto out;
		if (read(handle->fd, buf, 4) != 4)
			goto out;
		chunks[i].zsize = tep_read_number(handle->tep, buf, 4);
		chunks[i].offset = size;
		if (read(handle->fd, buf, 4) != 4)
			goto out;
		chunks[i].size = tep_read_number(handle->tep, buf, 4);
		size += chunks[i].size;
		if (lseek(handle->fd, chunks[i].zsize, SEEK_CUR) == (off_t)-1)
			goto out;
	}

	ret = count;
out:
	if (lseek(handle->fd, offset, SEEK_SET) == (off_t)-1)
		ret = -1;

	if (ret > 0 && chunks_info)
		*chunks_info = chunks;
	else
		free(chunks);

	return ret;
}

/**
 * tracecmd_uncompress_chunk - Uncompress given compression chunk.
 * @handle: compression handle
 * @chunk: chunk, that will be uncompressed in @data
 * @data: Preallocated memory for uncompressed data. Must have enough space
 * to hold the uncompressed data.
 *
 * This function uncompresses the chunk described by @chunk and stores
 * the uncompressed data in the preallocated memory @data.
 *
 * On success 0 is returned and the uncompressed data is stored in @data.
 * On error -1 is returned.
 */
int tracecmd_uncompress_chunk(struct tracecmd_compression *handle,
			      struct tracecmd_compress_chunk *chunk, char *data)
{
	char *bytes_in = NULL;
	int ret = -1;

	if (!handle || !handle->proto || !handle->proto->uncompress_block || !chunk || !data)
		return -1;

	if (lseek(handle->fd, chunk->zoffset + 8, SEEK_SET) == (off_t)-1)
		return -1;

	bytes_in = malloc(chunk->zsize);
	if (!bytes_in)
		return -1;

	if (read_fd(handle->fd, bytes_in, chunk->zsize) < 0)
		goto out;

	if (handle->proto->uncompress_block(handle->context,
					    bytes_in, chunk->zsize, data, chunk->size) < 0)
		goto out;

	ret = 0;
out:
	free(bytes_in);
	return ret;
}

/**
 * tracecmd_uncompress_copy_to - Uncompress data and copy to a file
 * @handle: compression handle
 * @fd: file descriptor to uncompressed data to copy into
 * @read_size: return, size of the compressed data read from @handle
 * @write_size: return, size of the uncompressed data written into @fd
 *
 * This function reads compressed data from the fd, associated with @handle,
 * uncompresses it using the @handle compression context and writes
 * the uncompressed data into the fd. The compressed data must be in the format:
 *  - 4 bytes, chunks count
 *  - for each chunk:
 *    - 4 bytes, size of compressed data in this chunk
 *    - 4 bytes, uncompressed size of the data in this chunk
 *    - data, bytes of <size of compressed data in this chunk>
 *
 * On success 0 is returned, @read_size and @write_size are updated with
 * the size of read and written data.
 */
int tracecmd_uncompress_copy_to(struct tracecmd_compression *handle, int fd,
				unsigned long long *read_size, unsigned long long *write_size)
{
	unsigned int s_uncompressed;
	unsigned int s_compressed;
	unsigned int rsize = 0;
	unsigned int wsize = 0;
	char *bytes_out = NULL;
	char *bytes_in = NULL;
	int size_out = 0;
	int size_in = 0;
	int chunks;
	char buf[4];
	char *tmp;
	int ret;

	if (!handle || !handle->proto || !handle->proto->uncompress_block)
		return -1;

	if (read(handle->fd, buf, 4) != 4)
		return -1;

	chunks = tep_read_number(handle->tep, buf, 4);
	rsize += 4;

	while (chunks) {
		if (read(handle->fd, buf, 4) != 4)
			break;

		s_compressed = tep_read_number(handle->tep, buf, 4);
		rsize += 4;
		if (read(handle->fd, buf, 4) != 4)
			break;

		s_uncompressed = tep_read_number(handle->tep, buf, 4);
		rsize += 4;
		if (!bytes_in || size_in < s_compressed) {
			tmp = realloc(bytes_in, s_compressed);
			if (!tmp)
				break;

			bytes_in = tmp;
			size_in = s_compressed;
		}

		if (!bytes_out || size_out < s_uncompressed) {
			tmp = realloc(bytes_out, s_uncompressed);
			if (!tmp)
				break;
			bytes_out = tmp;
			size_out = s_uncompressed;
		}

		if (read_fd(handle->fd, bytes_in, s_compressed) < 0)
			break;

		rsize += s_compressed;
		ret = handle->proto->uncompress_block(handle->context, bytes_in, s_compressed,
						      bytes_out, s_uncompressed);
		if (ret < 0)
			break;

		write_fd(fd, bytes_out, ret);
		wsize += ret;
		chunks--;
	}
	free(bytes_in);
	free(bytes_out);
	if (chunks)
		return -1;

	if (read_size)
		*read_size = rsize;
	if (write_size)
		*write_size = wsize;

	return 0;
}
