aboutsummaryrefslogtreecommitdiff
path: root/src/common/torgzip.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/common/torgzip.c')
-rw-r--r--src/common/torgzip.c171
1 files changed, 115 insertions, 56 deletions
diff --git a/src/common/torgzip.c b/src/common/torgzip.c
index 15451ee30d..c44399aa74 100644
--- a/src/common/torgzip.c
+++ b/src/common/torgzip.c
@@ -1,6 +1,6 @@
/* Copyright (c) 2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2013, The Tor Project, Inc. */
+ * Copyright (c) 2007-2016, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -46,27 +46,15 @@
#include <zlib.h>
-/** Set to 1 if zlib is a version that supports gzip; set to 0 if it doesn't;
- * set to -1 if we haven't checked yet. */
-static int gzip_is_supported = -1;
-
-/** Return true iff we support gzip-based compression. Otherwise, we need to
- * use zlib. */
-int
-is_gzip_supported(void)
-{
- if (gzip_is_supported >= 0)
- return gzip_is_supported;
+#if defined ZLIB_VERNUM && ZLIB_VERNUM < 0x1200
+#error "We require zlib version 1.2 or later."
+#endif
- if (!strcmpstart(ZLIB_VERSION, "0.") ||
- !strcmpstart(ZLIB_VERSION, "1.0") ||
- !strcmpstart(ZLIB_VERSION, "1.1"))
- gzip_is_supported = 0;
- else
- gzip_is_supported = 1;
+static size_t tor_zlib_state_size_precalc(int inflate,
+ int windowbits, int memlevel);
- return gzip_is_supported;
-}
+/** Total number of bytes allocated for zlib state */
+static size_t total_zlib_allocation = 0;
/** Return a string representation of the version of the currently running
* version of zlib. */
@@ -85,11 +73,28 @@ tor_zlib_get_header_version_str(void)
}
/** Return the 'bits' value to tell zlib to use <b>method</b>.*/
-static INLINE int
-method_bits(compress_method_t method)
+static inline int
+method_bits(compress_method_t method, zlib_compression_level_t level)
{
/* Bits+16 means "use gzip" in zlib >= 1.2 */
- return method == GZIP_METHOD ? 15+16 : 15;
+ const int flag = method == GZIP_METHOD ? 16 : 0;
+ switch (level) {
+ default:
+ case HIGH_COMPRESSION: return flag + 15;
+ case MEDIUM_COMPRESSION: return flag + 13;
+ case LOW_COMPRESSION: return flag + 11;
+ }
+}
+
+static inline int
+get_memlevel(zlib_compression_level_t level)
+{
+ switch (level) {
+ default:
+ case HIGH_COMPRESSION: return 8;
+ case MEDIUM_COMPRESSION: return 7;
+ case LOW_COMPRESSION: return 6;
+ }
}
/** @{ */
@@ -142,12 +147,6 @@ tor_gzip_compress(char **out, size_t *out_len,
*out = NULL;
- if (method == GZIP_METHOD && !is_gzip_supported()) {
- /* Old zlib version don't support gzip in deflateInit2 */
- log_warn(LD_BUG, "Gzip not supported with zlib %s", ZLIB_VERSION);
- goto err;
- }
-
stream = tor_malloc_zero(sizeof(struct z_stream_s));
stream->zalloc = Z_NULL;
stream->zfree = Z_NULL;
@@ -156,11 +155,14 @@ tor_gzip_compress(char **out, size_t *out_len,
stream->avail_in = (unsigned int)in_len;
if (deflateInit2(stream, Z_BEST_COMPRESSION, Z_DEFLATED,
- method_bits(method),
- 8, Z_DEFAULT_STRATEGY) != Z_OK) {
+ method_bits(method, HIGH_COMPRESSION),
+ get_memlevel(HIGH_COMPRESSION),
+ Z_DEFAULT_STRATEGY) != Z_OK) {
+ //LCOV_EXCL_START -- we can only provoke failure by giving junk arguments.
log_warn(LD_GENERAL, "Error from deflateInit2: %s",
stream->msg?stream->msg:"<no message>");
goto err;
+ //LCOV_EXCL_STOP
}
/* Guess 50% compression. */
@@ -179,6 +181,7 @@ tor_gzip_compress(char **out, size_t *out_len,
/* In case zlib doesn't work as I think .... */
if (stream->avail_out >= stream->avail_in+16)
break;
+ /* Falls through. */
case Z_BUF_ERROR:
offset = stream->next_out - ((unsigned char*)*out);
old_size = out_size;
@@ -213,13 +216,12 @@ tor_gzip_compress(char **out, size_t *out_len,
* the newly unsigned field isn't negative." */
tor_assert(stream->total_out >= 0);
#endif
- if (((size_t)stream->total_out) > out_size + 4097) {
- /* If we're wasting more than 4k, don't. */
- *out = tor_realloc(*out, stream->total_out + 1);
- }
if (deflateEnd(stream)!=Z_OK) {
+ // LCOV_EXCL_START -- unreachable if we handled the zlib structure right
+ tor_assert_nonfatal_unreached();
log_warn(LD_BUG, "Error freeing gzip structures");
goto err;
+ // LCOV_EXCL_STOP
}
tor_free(stream);
@@ -267,12 +269,6 @@ tor_gzip_uncompress(char **out, size_t *out_len,
tor_assert(in);
tor_assert(in_len < UINT_MAX);
- if (method == GZIP_METHOD && !is_gzip_supported()) {
- /* Old zlib version don't support gzip in inflateInit2 */
- log_warn(LD_BUG, "Gzip not supported with zlib %s", ZLIB_VERSION);
- return -1;
- }
-
*out = NULL;
stream = tor_malloc_zero(sizeof(struct z_stream_s));
@@ -283,10 +279,12 @@ tor_gzip_uncompress(char **out, size_t *out_len,
stream->avail_in = (unsigned int)in_len;
if (inflateInit2(stream,
- method_bits(method)) != Z_OK) {
+ method_bits(method, HIGH_COMPRESSION)) != Z_OK) {
+ // LCOV_EXCL_START -- can only hit this if we give bad inputs.
log_warn(LD_GENERAL, "Error from inflateInit2: %s",
stream->msg?stream->msg:"<no message>");
goto err;
+ // LCOV_EXCL_STOP
}
out_size = in_len * 2; /* guess 50% compression. */
@@ -309,7 +307,8 @@ tor_gzip_uncompress(char **out, size_t *out_len,
log_warn(LD_BUG, "Error freeing gzip structures");
goto err;
}
- if (inflateInit2(stream, method_bits(method)) != Z_OK) {
+ if (inflateInit2(stream,
+ method_bits(method,HIGH_COMPRESSION)) != Z_OK) {
log_warn(LD_GENERAL, "Error from second inflateInit2: %s",
stream->msg?stream->msg:"<no message>");
goto err;
@@ -321,6 +320,7 @@ tor_gzip_uncompress(char **out, size_t *out_len,
/* In case zlib doesn't work as I think.... */
if (stream->avail_out >= stream->avail_in+16)
break;
+ /* Falls through. */
case Z_BUF_ERROR:
if (stream->avail_out > 0) {
log_fn(protocol_warn_level, LD_PROTOCOL,
@@ -411,35 +411,47 @@ struct tor_zlib_state_t {
size_t input_so_far;
/** Number of bytes written so far. Used to detect zlib bombs. */
size_t output_so_far;
+
+ /** Approximate number of bytes allocated for this object. */
+ size_t allocation;
};
/** Construct and return a tor_zlib_state_t object using <b>method</b>. If
* <b>compress</b>, it's for compression; otherwise it's for
* decompression. */
tor_zlib_state_t *
-tor_zlib_new(int compress, compress_method_t method)
+tor_zlib_new(int compress_, compress_method_t method,
+ zlib_compression_level_t compression_level)
{
tor_zlib_state_t *out;
+ int bits, memlevel;
- if (method == GZIP_METHOD && !is_gzip_supported()) {
- /* Old zlib version don't support gzip in inflateInit2 */
- log_warn(LD_BUG, "Gzip not supported with zlib %s", ZLIB_VERSION);
- return NULL;
+ if (! compress_) {
+ /* use this setting for decompression, since we might have the
+ * max number of window bits */
+ compression_level = HIGH_COMPRESSION;
}
out = tor_malloc_zero(sizeof(tor_zlib_state_t));
out->stream.zalloc = Z_NULL;
out->stream.zfree = Z_NULL;
out->stream.opaque = NULL;
- out->compress = compress;
- if (compress) {
+ out->compress = compress_;
+ bits = method_bits(method, compression_level);
+ memlevel = get_memlevel(compression_level);
+ if (compress_) {
if (deflateInit2(&out->stream, Z_BEST_COMPRESSION, Z_DEFLATED,
- method_bits(method), 8, Z_DEFAULT_STRATEGY) != Z_OK)
- goto err;
+ bits, memlevel,
+ Z_DEFAULT_STRATEGY) != Z_OK)
+ goto err; // LCOV_EXCL_LINE
} else {
- if (inflateInit2(&out->stream, method_bits(method)) != Z_OK)
- goto err;
+ if (inflateInit2(&out->stream, bits) != Z_OK)
+ goto err; // LCOV_EXCL_LINE
}
+ out->allocation = tor_zlib_state_size_precalc(!compress_, bits, memlevel);
+
+ total_zlib_allocation += out->allocation;
+
return out;
err:
@@ -472,7 +484,7 @@ tor_zlib_process(tor_zlib_state_t *state,
state->stream.avail_out = (unsigned int)*out_len;
if (state->compress) {
- err = deflate(&state->stream, finish ? Z_FINISH : Z_SYNC_FLUSH);
+ err = deflate(&state->stream, finish ? Z_FINISH : Z_NO_FLUSH);
} else {
err = inflate(&state->stream, finish ? Z_FINISH : Z_SYNC_FLUSH);
}
@@ -496,7 +508,7 @@ tor_zlib_process(tor_zlib_state_t *state,
case Z_STREAM_END:
return TOR_ZLIB_DONE;
case Z_BUF_ERROR:
- if (state->stream.avail_in == 0)
+ if (state->stream.avail_in == 0 && !finish)
return TOR_ZLIB_OK;
return TOR_ZLIB_BUF_FULL;
case Z_OK:
@@ -517,6 +529,8 @@ tor_zlib_free(tor_zlib_state_t *state)
if (!state)
return;
+ total_zlib_allocation -= state->allocation;
+
if (state->compress)
deflateEnd(&state->stream);
else
@@ -525,3 +539,48 @@ tor_zlib_free(tor_zlib_state_t *state)
tor_free(state);
}
+/** Return an approximate number of bytes used in RAM to hold a state with
+ * window bits <b>windowBits</b> and compression level 'memlevel' */
+static size_t
+tor_zlib_state_size_precalc(int inflate_, int windowbits, int memlevel)
+{
+ windowbits &= 15;
+
+#define A_FEW_KILOBYTES 2048
+
+ if (inflate_) {
+ /* From zconf.h:
+
+ "The memory requirements for inflate are (in bytes) 1 << windowBits
+ that is, 32K for windowBits=15 (default value) plus a few kilobytes
+ for small objects."
+ */
+ return sizeof(tor_zlib_state_t) + sizeof(struct z_stream_s) +
+ (1 << 15) + A_FEW_KILOBYTES;
+ } else {
+ /* Also from zconf.h:
+
+ "The memory requirements for deflate are (in bytes):
+ (1 << (windowBits+2)) + (1 << (memLevel+9))
+ ... plus a few kilobytes for small objects."
+ */
+ return sizeof(tor_zlib_state_t) + sizeof(struct z_stream_s) +
+ (1 << (windowbits + 2)) + (1 << (memlevel + 9)) + A_FEW_KILOBYTES;
+ }
+#undef A_FEW_KILOBYTES
+}
+
+/** Return the approximate number of bytes allocated for <b>state</b>. */
+size_t
+tor_zlib_state_size(const tor_zlib_state_t *state)
+{
+ return state->allocation;
+}
+
+/** Return the approximate number of bytes allocated for all zlib states. */
+size_t
+tor_zlib_get_total_allocation(void)
+{
+ return total_zlib_allocation;
+}
+