blob: 4ee05977d5ff514ba3ff6c538021cdc8f13a0acf [file] [log] [blame] [raw]
/*
* -----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* Lukas Niederbremer <webmaster@flippeh.de> and Clark Gaebel <cg.wowus.cg@gmail.com>
* wrote this file. As long as you retain this notice you can do whatever you
* want with this stuff. If we meet some day, and you think this stuff is worth
* it, you can buy us a beer in return.
* -----------------------------------------------------------------------------
*/
#include "nbt.h"
#include "buffer.h"
#include "list.h"
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <zlib.h>
/*
* zlib resources:
*
* http://zlib.net/manual.html
* http://zlib.net/zlib_how.html
* http://www.gzip.org/zlib/zlib_faq.html
*/
/* The number of bytes to process at a time */
#define CHUNK_SIZE 4096
/*
* Reads a whole file into a buffer. Returns a NULL buffer and sets errno on
* error.
*/
static struct buffer read_file(FILE* fp)
{
struct buffer ret = BUFFER_INIT;
size_t bytes_read;
do {
if(buffer_reserve(&ret, ret.len + CHUNK_SIZE))
return (errno = NBT_EMEM), buffer_free(&ret), BUFFER_INIT;
bytes_read = fread(ret.data + ret.len, 1, CHUNK_SIZE, fp);
ret.len += bytes_read;
if(ferror(fp))
return (errno = NBT_EIO), buffer_free(&ret), BUFFER_INIT;
} while(!feof(fp));
return ret;
}
static nbt_status write_file(FILE* fp, const void* data, size_t len)
{
const char* cdata = data;
size_t bytes_left = len;
size_t bytes_written;
do {
bytes_written = fwrite(cdata, 1, bytes_left, fp);
if(ferror(fp)) return NBT_EIO;
bytes_left -= bytes_written;
cdata += bytes_written;
} while(bytes_left > 0);
return NBT_OK;
}
/*
* Reads in uncompressed data and returns a buffer with the $(strat)-compressed
* data within. Returns a NULL buffer on failure, and sets errno appropriately.
*/
static struct buffer __compress(const void* mem,
size_t len,
nbt_compression_strategy strat)
{
struct buffer ret = BUFFER_INIT;
errno = NBT_OK;
z_stream stream = {
.zalloc = Z_NULL,
.zfree = Z_NULL,
.opaque = Z_NULL,
.next_in = (void*)mem,
.avail_in = len
};
/* "The default value is 15"... */
int windowbits = 15;
/* ..."Add 16 to windowBits to write a simple gzip header and trailer around
* the compressed data instead of a zlib wrapper." */
if(strat == STRAT_GZIP)
windowbits += 16;
if(deflateInit2(&stream,
Z_DEFAULT_COMPRESSION,
Z_DEFLATED,
windowbits,
8,
Z_DEFAULT_STRATEGY
) != Z_OK)
{
errno = NBT_EZ;
return BUFFER_INIT;
}
assert(stream.avail_in == len); /* I'm not sure if zlib will clobber this */
do {
if(buffer_reserve(&ret, ret.len + CHUNK_SIZE))
{
errno = NBT_EMEM;
goto compression_error;
}
stream.next_out = ret.data + ret.len;
stream.avail_out = CHUNK_SIZE;
if(deflate(&stream, Z_FINISH) == Z_STREAM_ERROR)
goto compression_error;
ret.len += CHUNK_SIZE - stream.avail_out;
} while(stream.avail_out == 0);
(void)deflateEnd(&stream);
return ret;
compression_error:
if(errno == NBT_OK)
errno = NBT_EZ;
(void)deflateEnd(&stream);
buffer_free(&ret);
return BUFFER_INIT;
}
/*
* Reads in zlib-compressed data, and returns a buffer with the decompressed
* data within. Returns a NULL buffer on failure, and sets errno appropriately.
*/
static struct buffer __decompress(const void* mem, size_t len)
{
struct buffer ret = BUFFER_INIT;
errno = NBT_OK;
z_stream stream = {
.zalloc = Z_NULL,
.zfree = Z_NULL,
.opaque = Z_NULL,
.next_in = (void*)mem,
.avail_in = len
};
/* "Add 32 to windowBits to enable zlib and gzip decoding with automatic
* header detection" */
if(inflateInit2(&stream, 15 + 32) != Z_OK)
{
errno = NBT_EZ;
return BUFFER_INIT;
}
int zlib_ret;
do {
if(buffer_reserve(&ret, ret.len + CHUNK_SIZE))
{
errno = NBT_EMEM;
goto decompression_error;
}
stream.avail_out = CHUNK_SIZE;
stream.next_out = (unsigned char*)ret.data + ret.len;
switch((zlib_ret = inflate(&stream, Z_NO_FLUSH)))
{
case Z_MEM_ERROR:
errno = NBT_EMEM;
/* fall through */
case Z_DATA_ERROR: case Z_NEED_DICT:
goto decompression_error;
default:
/* update our buffer length to reflect the new data */
ret.len += CHUNK_SIZE - stream.avail_out;
}
} while(stream.avail_out == 0);
/*
* If we're at the end of the input data, we'd sure as hell be at the end
* of the zlib stream.
*/
if(zlib_ret != Z_STREAM_END) goto decompression_error;
(void)inflateEnd(&stream);
return ret;
decompression_error:
if(errno == NBT_OK)
errno = NBT_EZ;
(void)inflateEnd(&stream);
buffer_free(&ret);
return BUFFER_INIT;
}
/*
* No incremental parsing goes on. We just dump the whole compressed file into
* memory then pass the job off to nbt_parse_chunk.
*/
nbt_node* nbt_parse_file(FILE* fp)
{
errno = NBT_OK;
struct buffer compressed = read_file(fp);
if(compressed.data == NULL)
return NULL;
nbt_node* ret = nbt_parse_compressed(compressed.data, compressed.len);
buffer_free(&compressed);
return ret;
}
nbt_node* nbt_parse_path(const char* filename)
{
FILE* fp = fopen(filename, "rb");
if(fp == NULL)
{
errno = NBT_EIO;
return NULL;
}
nbt_node* r = nbt_parse_file(fp);
fclose(fp);
return r;
}
nbt_node* nbt_parse_compressed(const void* chunk_start, size_t length)
{
struct buffer decompressed = __decompress(chunk_start, length);
if(decompressed.data == NULL)
return NULL;
nbt_node* ret = nbt_parse(decompressed.data, decompressed.len);
buffer_free(&decompressed);
return ret;
}
/*
* Once again, all we're doing is handing the actual compression off to
* nbt_dump_compressed, then dumping it into the file.
*/
nbt_status nbt_dump_file(const nbt_node* tree, FILE* fp, nbt_compression_strategy strat)
{
struct buffer compressed = nbt_dump_compressed(tree, strat);
if(compressed.data == NULL)
return (nbt_status)errno;
nbt_status ret = write_file(fp, compressed.data, compressed.len);
buffer_free(&compressed);
return ret;
}
struct buffer nbt_dump_compressed(const nbt_node* tree, nbt_compression_strategy strat)
{
struct buffer uncompressed = nbt_dump_binary(tree);
if(uncompressed.data == NULL)
return BUFFER_INIT;
struct buffer compressed = __compress(uncompressed.data, uncompressed.len, strat);
buffer_free(&uncompressed);
return compressed;
}