Tue, 06 Jan 2015 21:39:09 +0100
Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
michael@0 | 1 | // Copyright 2011 Google Inc. All Rights Reserved. |
michael@0 | 2 | // |
michael@0 | 3 | // Redistribution and use in source and binary forms, with or without |
michael@0 | 4 | // modification, are permitted provided that the following conditions are |
michael@0 | 5 | // met: |
michael@0 | 6 | // |
michael@0 | 7 | // * Redistributions of source code must retain the above copyright |
michael@0 | 8 | // notice, this list of conditions and the following disclaimer. |
michael@0 | 9 | // * Redistributions in binary form must reproduce the above |
michael@0 | 10 | // copyright notice, this list of conditions and the following disclaimer |
michael@0 | 11 | // in the documentation and/or other materials provided with the |
michael@0 | 12 | // distribution. |
michael@0 | 13 | // * Neither the name of Google Inc. nor the names of its |
michael@0 | 14 | // contributors may be used to endorse or promote products derived from |
michael@0 | 15 | // this software without specific prior written permission. |
michael@0 | 16 | // |
michael@0 | 17 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
michael@0 | 18 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
michael@0 | 19 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
michael@0 | 20 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
michael@0 | 21 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
michael@0 | 22 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
michael@0 | 23 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
michael@0 | 24 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
michael@0 | 25 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
michael@0 | 26 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
michael@0 | 27 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
michael@0 | 28 | // |
michael@0 | 29 | // Various stubs for the unit tests for the open-source version of Snappy. |
michael@0 | 30 | |
michael@0 | 31 | #include "snappy-test.h" |
michael@0 | 32 | |
michael@0 | 33 | #ifdef HAVE_WINDOWS_H |
michael@0 | 34 | #define WIN32_LEAN_AND_MEAN |
michael@0 | 35 | #include <windows.h> |
michael@0 | 36 | #endif |
michael@0 | 37 | |
michael@0 | 38 | #include <algorithm> |
michael@0 | 39 | |
michael@0 | 40 | DEFINE_bool(run_microbenchmarks, true, |
michael@0 | 41 | "Run microbenchmarks before doing anything else."); |
michael@0 | 42 | |
michael@0 | 43 | namespace snappy { |
michael@0 | 44 | |
michael@0 | 45 | string ReadTestDataFile(const string& base) { |
michael@0 | 46 | string contents; |
michael@0 | 47 | const char* srcdir = getenv("srcdir"); // This is set by Automake. |
michael@0 | 48 | if (srcdir) { |
michael@0 | 49 | File::ReadFileToStringOrDie( |
michael@0 | 50 | string(srcdir) + "/testdata/" + base, &contents); |
michael@0 | 51 | } else { |
michael@0 | 52 | File::ReadFileToStringOrDie("testdata/" + base, &contents); |
michael@0 | 53 | } |
michael@0 | 54 | return contents; |
michael@0 | 55 | } |
michael@0 | 56 | |
michael@0 | 57 | string StringPrintf(const char* format, ...) { |
michael@0 | 58 | char buf[4096]; |
michael@0 | 59 | va_list ap; |
michael@0 | 60 | va_start(ap, format); |
michael@0 | 61 | vsnprintf(buf, sizeof(buf), format, ap); |
michael@0 | 62 | va_end(ap); |
michael@0 | 63 | return buf; |
michael@0 | 64 | } |
michael@0 | 65 | |
michael@0 | 66 | bool benchmark_running = false; |
michael@0 | 67 | int64 benchmark_real_time_us = 0; |
michael@0 | 68 | int64 benchmark_cpu_time_us = 0; |
michael@0 | 69 | string *benchmark_label = NULL; |
michael@0 | 70 | int64 benchmark_bytes_processed = 0; |
michael@0 | 71 | |
michael@0 | 72 | void ResetBenchmarkTiming() { |
michael@0 | 73 | benchmark_real_time_us = 0; |
michael@0 | 74 | benchmark_cpu_time_us = 0; |
michael@0 | 75 | } |
michael@0 | 76 | |
michael@0 | 77 | #ifdef WIN32 |
michael@0 | 78 | LARGE_INTEGER benchmark_start_real; |
michael@0 | 79 | FILETIME benchmark_start_cpu; |
michael@0 | 80 | #else // WIN32 |
michael@0 | 81 | struct timeval benchmark_start_real; |
michael@0 | 82 | struct rusage benchmark_start_cpu; |
michael@0 | 83 | #endif // WIN32 |
michael@0 | 84 | |
michael@0 | 85 | void StartBenchmarkTiming() { |
michael@0 | 86 | #ifdef WIN32 |
michael@0 | 87 | QueryPerformanceCounter(&benchmark_start_real); |
michael@0 | 88 | FILETIME dummy; |
michael@0 | 89 | CHECK(GetProcessTimes( |
michael@0 | 90 | GetCurrentProcess(), &dummy, &dummy, &dummy, &benchmark_start_cpu)); |
michael@0 | 91 | #else |
michael@0 | 92 | gettimeofday(&benchmark_start_real, NULL); |
michael@0 | 93 | if (getrusage(RUSAGE_SELF, &benchmark_start_cpu) == -1) { |
michael@0 | 94 | perror("getrusage(RUSAGE_SELF)"); |
michael@0 | 95 | exit(1); |
michael@0 | 96 | } |
michael@0 | 97 | #endif |
michael@0 | 98 | benchmark_running = true; |
michael@0 | 99 | } |
michael@0 | 100 | |
michael@0 | 101 | void StopBenchmarkTiming() { |
michael@0 | 102 | if (!benchmark_running) { |
michael@0 | 103 | return; |
michael@0 | 104 | } |
michael@0 | 105 | |
michael@0 | 106 | #ifdef WIN32 |
michael@0 | 107 | LARGE_INTEGER benchmark_stop_real; |
michael@0 | 108 | LARGE_INTEGER benchmark_frequency; |
michael@0 | 109 | QueryPerformanceCounter(&benchmark_stop_real); |
michael@0 | 110 | QueryPerformanceFrequency(&benchmark_frequency); |
michael@0 | 111 | |
michael@0 | 112 | double elapsed_real = static_cast<double>( |
michael@0 | 113 | benchmark_stop_real.QuadPart - benchmark_start_real.QuadPart) / |
michael@0 | 114 | benchmark_frequency.QuadPart; |
michael@0 | 115 | benchmark_real_time_us += elapsed_real * 1e6 + 0.5; |
michael@0 | 116 | |
michael@0 | 117 | FILETIME benchmark_stop_cpu, dummy; |
michael@0 | 118 | CHECK(GetProcessTimes( |
michael@0 | 119 | GetCurrentProcess(), &dummy, &dummy, &dummy, &benchmark_stop_cpu)); |
michael@0 | 120 | |
michael@0 | 121 | ULARGE_INTEGER start_ulargeint; |
michael@0 | 122 | start_ulargeint.LowPart = benchmark_start_cpu.dwLowDateTime; |
michael@0 | 123 | start_ulargeint.HighPart = benchmark_start_cpu.dwHighDateTime; |
michael@0 | 124 | |
michael@0 | 125 | ULARGE_INTEGER stop_ulargeint; |
michael@0 | 126 | stop_ulargeint.LowPart = benchmark_stop_cpu.dwLowDateTime; |
michael@0 | 127 | stop_ulargeint.HighPart = benchmark_stop_cpu.dwHighDateTime; |
michael@0 | 128 | |
michael@0 | 129 | benchmark_cpu_time_us += |
michael@0 | 130 | (stop_ulargeint.QuadPart - start_ulargeint.QuadPart + 5) / 10; |
michael@0 | 131 | #else // WIN32 |
michael@0 | 132 | struct timeval benchmark_stop_real; |
michael@0 | 133 | gettimeofday(&benchmark_stop_real, NULL); |
michael@0 | 134 | benchmark_real_time_us += |
michael@0 | 135 | 1000000 * (benchmark_stop_real.tv_sec - benchmark_start_real.tv_sec); |
michael@0 | 136 | benchmark_real_time_us += |
michael@0 | 137 | (benchmark_stop_real.tv_usec - benchmark_start_real.tv_usec); |
michael@0 | 138 | |
michael@0 | 139 | struct rusage benchmark_stop_cpu; |
michael@0 | 140 | if (getrusage(RUSAGE_SELF, &benchmark_stop_cpu) == -1) { |
michael@0 | 141 | perror("getrusage(RUSAGE_SELF)"); |
michael@0 | 142 | exit(1); |
michael@0 | 143 | } |
michael@0 | 144 | benchmark_cpu_time_us += 1000000 * (benchmark_stop_cpu.ru_utime.tv_sec - |
michael@0 | 145 | benchmark_start_cpu.ru_utime.tv_sec); |
michael@0 | 146 | benchmark_cpu_time_us += (benchmark_stop_cpu.ru_utime.tv_usec - |
michael@0 | 147 | benchmark_start_cpu.ru_utime.tv_usec); |
michael@0 | 148 | #endif // WIN32 |
michael@0 | 149 | |
michael@0 | 150 | benchmark_running = false; |
michael@0 | 151 | } |
michael@0 | 152 | |
michael@0 | 153 | void SetBenchmarkLabel(const string& str) { |
michael@0 | 154 | if (benchmark_label) { |
michael@0 | 155 | delete benchmark_label; |
michael@0 | 156 | } |
michael@0 | 157 | benchmark_label = new string(str); |
michael@0 | 158 | } |
michael@0 | 159 | |
michael@0 | 160 | void SetBenchmarkBytesProcessed(int64 bytes) { |
michael@0 | 161 | benchmark_bytes_processed = bytes; |
michael@0 | 162 | } |
michael@0 | 163 | |
michael@0 | 164 | struct BenchmarkRun { |
michael@0 | 165 | int64 real_time_us; |
michael@0 | 166 | int64 cpu_time_us; |
michael@0 | 167 | }; |
michael@0 | 168 | |
michael@0 | 169 | struct BenchmarkCompareCPUTime { |
michael@0 | 170 | bool operator() (const BenchmarkRun& a, const BenchmarkRun& b) const { |
michael@0 | 171 | return a.cpu_time_us < b.cpu_time_us; |
michael@0 | 172 | } |
michael@0 | 173 | }; |
michael@0 | 174 | |
michael@0 | 175 | void Benchmark::Run() { |
michael@0 | 176 | for (int test_case_num = start_; test_case_num <= stop_; ++test_case_num) { |
michael@0 | 177 | // Run a few iterations first to find out approximately how fast |
michael@0 | 178 | // the benchmark is. |
michael@0 | 179 | const int kCalibrateIterations = 100; |
michael@0 | 180 | ResetBenchmarkTiming(); |
michael@0 | 181 | StartBenchmarkTiming(); |
michael@0 | 182 | (*function_)(kCalibrateIterations, test_case_num); |
michael@0 | 183 | StopBenchmarkTiming(); |
michael@0 | 184 | |
michael@0 | 185 | // Let each test case run for about 200ms, but at least as many |
michael@0 | 186 | // as we used to calibrate. |
michael@0 | 187 | // Run five times and pick the median. |
michael@0 | 188 | const int kNumRuns = 5; |
michael@0 | 189 | const int kMedianPos = kNumRuns / 2; |
michael@0 | 190 | int num_iterations = 0; |
michael@0 | 191 | if (benchmark_real_time_us > 0) { |
michael@0 | 192 | num_iterations = 200000 * kCalibrateIterations / benchmark_real_time_us; |
michael@0 | 193 | } |
michael@0 | 194 | num_iterations = max(num_iterations, kCalibrateIterations); |
michael@0 | 195 | BenchmarkRun benchmark_runs[kNumRuns]; |
michael@0 | 196 | |
michael@0 | 197 | for (int run = 0; run < kNumRuns; ++run) { |
michael@0 | 198 | ResetBenchmarkTiming(); |
michael@0 | 199 | StartBenchmarkTiming(); |
michael@0 | 200 | (*function_)(num_iterations, test_case_num); |
michael@0 | 201 | StopBenchmarkTiming(); |
michael@0 | 202 | |
michael@0 | 203 | benchmark_runs[run].real_time_us = benchmark_real_time_us; |
michael@0 | 204 | benchmark_runs[run].cpu_time_us = benchmark_cpu_time_us; |
michael@0 | 205 | } |
michael@0 | 206 | |
michael@0 | 207 | nth_element(benchmark_runs, |
michael@0 | 208 | benchmark_runs + kMedianPos, |
michael@0 | 209 | benchmark_runs + kNumRuns, |
michael@0 | 210 | BenchmarkCompareCPUTime()); |
michael@0 | 211 | int64 real_time_us = benchmark_runs[kMedianPos].real_time_us; |
michael@0 | 212 | int64 cpu_time_us = benchmark_runs[kMedianPos].cpu_time_us; |
michael@0 | 213 | int64 bytes_per_second = benchmark_bytes_processed * 1000000 / cpu_time_us; |
michael@0 | 214 | |
michael@0 | 215 | string heading = StringPrintf("%s/%d", name_.c_str(), test_case_num); |
michael@0 | 216 | string human_readable_speed; |
michael@0 | 217 | if (bytes_per_second < 1024) { |
michael@0 | 218 | human_readable_speed = StringPrintf("%dB/s", bytes_per_second); |
michael@0 | 219 | } else if (bytes_per_second < 1024 * 1024) { |
michael@0 | 220 | human_readable_speed = StringPrintf( |
michael@0 | 221 | "%.1fkB/s", bytes_per_second / 1024.0f); |
michael@0 | 222 | } else if (bytes_per_second < 1024 * 1024 * 1024) { |
michael@0 | 223 | human_readable_speed = StringPrintf( |
michael@0 | 224 | "%.1fMB/s", bytes_per_second / (1024.0f * 1024.0f)); |
michael@0 | 225 | } else { |
michael@0 | 226 | human_readable_speed = StringPrintf( |
michael@0 | 227 | "%.1fGB/s", bytes_per_second / (1024.0f * 1024.0f * 1024.0f)); |
michael@0 | 228 | } |
michael@0 | 229 | |
michael@0 | 230 | fprintf(stderr, |
michael@0 | 231 | #ifdef WIN32 |
michael@0 | 232 | "%-18s %10I64d %10I64d %10d %s %s\n", |
michael@0 | 233 | #else |
michael@0 | 234 | "%-18s %10lld %10lld %10d %s %s\n", |
michael@0 | 235 | #endif |
michael@0 | 236 | heading.c_str(), |
michael@0 | 237 | static_cast<long long>(real_time_us * 1000 / num_iterations), |
michael@0 | 238 | static_cast<long long>(cpu_time_us * 1000 / num_iterations), |
michael@0 | 239 | num_iterations, |
michael@0 | 240 | human_readable_speed.c_str(), |
michael@0 | 241 | benchmark_label->c_str()); |
michael@0 | 242 | } |
michael@0 | 243 | } |
michael@0 | 244 | |
michael@0 | 245 | #ifdef HAVE_LIBZ |
michael@0 | 246 | |
michael@0 | 247 | ZLib::ZLib() |
michael@0 | 248 | : comp_init_(false), |
michael@0 | 249 | uncomp_init_(false) { |
michael@0 | 250 | Reinit(); |
michael@0 | 251 | } |
michael@0 | 252 | |
michael@0 | 253 | ZLib::~ZLib() { |
michael@0 | 254 | if (comp_init_) { deflateEnd(&comp_stream_); } |
michael@0 | 255 | if (uncomp_init_) { inflateEnd(&uncomp_stream_); } |
michael@0 | 256 | } |
michael@0 | 257 | |
michael@0 | 258 | void ZLib::Reinit() { |
michael@0 | 259 | compression_level_ = Z_DEFAULT_COMPRESSION; |
michael@0 | 260 | window_bits_ = MAX_WBITS; |
michael@0 | 261 | mem_level_ = 8; // DEF_MEM_LEVEL |
michael@0 | 262 | if (comp_init_) { |
michael@0 | 263 | deflateEnd(&comp_stream_); |
michael@0 | 264 | comp_init_ = false; |
michael@0 | 265 | } |
michael@0 | 266 | if (uncomp_init_) { |
michael@0 | 267 | inflateEnd(&uncomp_stream_); |
michael@0 | 268 | uncomp_init_ = false; |
michael@0 | 269 | } |
michael@0 | 270 | first_chunk_ = true; |
michael@0 | 271 | } |
michael@0 | 272 | |
michael@0 | 273 | void ZLib::Reset() { |
michael@0 | 274 | first_chunk_ = true; |
michael@0 | 275 | } |
michael@0 | 276 | |
michael@0 | 277 | // --------- COMPRESS MODE |
michael@0 | 278 | |
michael@0 | 279 | // Initialization method to be called if we hit an error while |
michael@0 | 280 | // compressing. On hitting an error, call this method before returning |
michael@0 | 281 | // the error. |
michael@0 | 282 | void ZLib::CompressErrorInit() { |
michael@0 | 283 | deflateEnd(&comp_stream_); |
michael@0 | 284 | comp_init_ = false; |
michael@0 | 285 | Reset(); |
michael@0 | 286 | } |
michael@0 | 287 | |
michael@0 | 288 | int ZLib::DeflateInit() { |
michael@0 | 289 | return deflateInit2(&comp_stream_, |
michael@0 | 290 | compression_level_, |
michael@0 | 291 | Z_DEFLATED, |
michael@0 | 292 | window_bits_, |
michael@0 | 293 | mem_level_, |
michael@0 | 294 | Z_DEFAULT_STRATEGY); |
michael@0 | 295 | } |
michael@0 | 296 | |
michael@0 | 297 | int ZLib::CompressInit(Bytef *dest, uLongf *destLen, |
michael@0 | 298 | const Bytef *source, uLong *sourceLen) { |
michael@0 | 299 | int err; |
michael@0 | 300 | |
michael@0 | 301 | comp_stream_.next_in = (Bytef*)source; |
michael@0 | 302 | comp_stream_.avail_in = (uInt)*sourceLen; |
michael@0 | 303 | if ((uLong)comp_stream_.avail_in != *sourceLen) return Z_BUF_ERROR; |
michael@0 | 304 | comp_stream_.next_out = dest; |
michael@0 | 305 | comp_stream_.avail_out = (uInt)*destLen; |
michael@0 | 306 | if ((uLong)comp_stream_.avail_out != *destLen) return Z_BUF_ERROR; |
michael@0 | 307 | |
michael@0 | 308 | if ( !first_chunk_ ) // only need to set up stream the first time through |
michael@0 | 309 | return Z_OK; |
michael@0 | 310 | |
michael@0 | 311 | if (comp_init_) { // we've already initted it |
michael@0 | 312 | err = deflateReset(&comp_stream_); |
michael@0 | 313 | if (err != Z_OK) { |
michael@0 | 314 | LOG(WARNING) << "ERROR: Can't reset compress object; creating a new one"; |
michael@0 | 315 | deflateEnd(&comp_stream_); |
michael@0 | 316 | comp_init_ = false; |
michael@0 | 317 | } |
michael@0 | 318 | } |
michael@0 | 319 | if (!comp_init_) { // first use |
michael@0 | 320 | comp_stream_.zalloc = (alloc_func)0; |
michael@0 | 321 | comp_stream_.zfree = (free_func)0; |
michael@0 | 322 | comp_stream_.opaque = (voidpf)0; |
michael@0 | 323 | err = DeflateInit(); |
michael@0 | 324 | if (err != Z_OK) return err; |
michael@0 | 325 | comp_init_ = true; |
michael@0 | 326 | } |
michael@0 | 327 | return Z_OK; |
michael@0 | 328 | } |
michael@0 | 329 | |
michael@0 | 330 | // In a perfect world we'd always have the full buffer to compress |
michael@0 | 331 | // when the time came, and we could just call Compress(). Alas, we |
michael@0 | 332 | // want to do chunked compression on our webserver. In this |
michael@0 | 333 | // application, we compress the header, send it off, then compress the |
michael@0 | 334 | // results, send them off, then compress the footer. Thus we need to |
michael@0 | 335 | // use the chunked compression features of zlib. |
michael@0 | 336 | int ZLib::CompressAtMostOrAll(Bytef *dest, uLongf *destLen, |
michael@0 | 337 | const Bytef *source, uLong *sourceLen, |
michael@0 | 338 | int flush_mode) { // Z_FULL_FLUSH or Z_FINISH |
michael@0 | 339 | int err; |
michael@0 | 340 | |
michael@0 | 341 | if ( (err=CompressInit(dest, destLen, source, sourceLen)) != Z_OK ) |
michael@0 | 342 | return err; |
michael@0 | 343 | |
michael@0 | 344 | // This is used to figure out how many bytes we wrote *this chunk* |
michael@0 | 345 | int compressed_size = comp_stream_.total_out; |
michael@0 | 346 | |
michael@0 | 347 | // Some setup happens only for the first chunk we compress in a run |
michael@0 | 348 | if ( first_chunk_ ) { |
michael@0 | 349 | first_chunk_ = false; |
michael@0 | 350 | } |
michael@0 | 351 | |
michael@0 | 352 | // flush_mode is Z_FINISH for all mode, Z_SYNC_FLUSH for incremental |
michael@0 | 353 | // compression. |
michael@0 | 354 | err = deflate(&comp_stream_, flush_mode); |
michael@0 | 355 | |
michael@0 | 356 | *sourceLen = comp_stream_.avail_in; |
michael@0 | 357 | |
michael@0 | 358 | if ((err == Z_STREAM_END || err == Z_OK) |
michael@0 | 359 | && comp_stream_.avail_in == 0 |
michael@0 | 360 | && comp_stream_.avail_out != 0 ) { |
michael@0 | 361 | // we processed everything ok and the output buffer was large enough. |
michael@0 | 362 | ; |
michael@0 | 363 | } else if (err == Z_STREAM_END && comp_stream_.avail_in > 0) { |
michael@0 | 364 | return Z_BUF_ERROR; // should never happen |
michael@0 | 365 | } else if (err != Z_OK && err != Z_STREAM_END && err != Z_BUF_ERROR) { |
michael@0 | 366 | // an error happened |
michael@0 | 367 | CompressErrorInit(); |
michael@0 | 368 | return err; |
michael@0 | 369 | } else if (comp_stream_.avail_out == 0) { // not enough space |
michael@0 | 370 | err = Z_BUF_ERROR; |
michael@0 | 371 | } |
michael@0 | 372 | |
michael@0 | 373 | assert(err == Z_OK || err == Z_STREAM_END || err == Z_BUF_ERROR); |
michael@0 | 374 | if (err == Z_STREAM_END) |
michael@0 | 375 | err = Z_OK; |
michael@0 | 376 | |
michael@0 | 377 | // update the crc and other metadata |
michael@0 | 378 | compressed_size = comp_stream_.total_out - compressed_size; // delta |
michael@0 | 379 | *destLen = compressed_size; |
michael@0 | 380 | |
michael@0 | 381 | return err; |
michael@0 | 382 | } |
michael@0 | 383 | |
michael@0 | 384 | int ZLib::CompressChunkOrAll(Bytef *dest, uLongf *destLen, |
michael@0 | 385 | const Bytef *source, uLong sourceLen, |
michael@0 | 386 | int flush_mode) { // Z_FULL_FLUSH or Z_FINISH |
michael@0 | 387 | const int ret = |
michael@0 | 388 | CompressAtMostOrAll(dest, destLen, source, &sourceLen, flush_mode); |
michael@0 | 389 | if (ret == Z_BUF_ERROR) |
michael@0 | 390 | CompressErrorInit(); |
michael@0 | 391 | return ret; |
michael@0 | 392 | } |
michael@0 | 393 | |
michael@0 | 394 | // This routine only initializes the compression stream once. Thereafter, it |
michael@0 | 395 | // just does a deflateReset on the stream, which should be faster. |
michael@0 | 396 | int ZLib::Compress(Bytef *dest, uLongf *destLen, |
michael@0 | 397 | const Bytef *source, uLong sourceLen) { |
michael@0 | 398 | int err; |
michael@0 | 399 | if ( (err=CompressChunkOrAll(dest, destLen, source, sourceLen, |
michael@0 | 400 | Z_FINISH)) != Z_OK ) |
michael@0 | 401 | return err; |
michael@0 | 402 | Reset(); // reset for next call to Compress |
michael@0 | 403 | |
michael@0 | 404 | return Z_OK; |
michael@0 | 405 | } |
michael@0 | 406 | |
michael@0 | 407 | |
michael@0 | 408 | // --------- UNCOMPRESS MODE |
michael@0 | 409 | |
michael@0 | 410 | int ZLib::InflateInit() { |
michael@0 | 411 | return inflateInit2(&uncomp_stream_, MAX_WBITS); |
michael@0 | 412 | } |
michael@0 | 413 | |
michael@0 | 414 | // Initialization method to be called if we hit an error while |
michael@0 | 415 | // uncompressing. On hitting an error, call this method before |
michael@0 | 416 | // returning the error. |
michael@0 | 417 | void ZLib::UncompressErrorInit() { |
michael@0 | 418 | inflateEnd(&uncomp_stream_); |
michael@0 | 419 | uncomp_init_ = false; |
michael@0 | 420 | Reset(); |
michael@0 | 421 | } |
michael@0 | 422 | |
michael@0 | 423 | int ZLib::UncompressInit(Bytef *dest, uLongf *destLen, |
michael@0 | 424 | const Bytef *source, uLong *sourceLen) { |
michael@0 | 425 | int err; |
michael@0 | 426 | |
michael@0 | 427 | uncomp_stream_.next_in = (Bytef*)source; |
michael@0 | 428 | uncomp_stream_.avail_in = (uInt)*sourceLen; |
michael@0 | 429 | // Check for source > 64K on 16-bit machine: |
michael@0 | 430 | if ((uLong)uncomp_stream_.avail_in != *sourceLen) return Z_BUF_ERROR; |
michael@0 | 431 | |
michael@0 | 432 | uncomp_stream_.next_out = dest; |
michael@0 | 433 | uncomp_stream_.avail_out = (uInt)*destLen; |
michael@0 | 434 | if ((uLong)uncomp_stream_.avail_out != *destLen) return Z_BUF_ERROR; |
michael@0 | 435 | |
michael@0 | 436 | if ( !first_chunk_ ) // only need to set up stream the first time through |
michael@0 | 437 | return Z_OK; |
michael@0 | 438 | |
michael@0 | 439 | if (uncomp_init_) { // we've already initted it |
michael@0 | 440 | err = inflateReset(&uncomp_stream_); |
michael@0 | 441 | if (err != Z_OK) { |
michael@0 | 442 | LOG(WARNING) |
michael@0 | 443 | << "ERROR: Can't reset uncompress object; creating a new one"; |
michael@0 | 444 | UncompressErrorInit(); |
michael@0 | 445 | } |
michael@0 | 446 | } |
michael@0 | 447 | if (!uncomp_init_) { |
michael@0 | 448 | uncomp_stream_.zalloc = (alloc_func)0; |
michael@0 | 449 | uncomp_stream_.zfree = (free_func)0; |
michael@0 | 450 | uncomp_stream_.opaque = (voidpf)0; |
michael@0 | 451 | err = InflateInit(); |
michael@0 | 452 | if (err != Z_OK) return err; |
michael@0 | 453 | uncomp_init_ = true; |
michael@0 | 454 | } |
michael@0 | 455 | return Z_OK; |
michael@0 | 456 | } |
michael@0 | 457 | |
michael@0 | 458 | // If you compressed your data a chunk at a time, with CompressChunk, |
michael@0 | 459 | // you can uncompress it a chunk at a time with UncompressChunk. |
michael@0 | 460 | // Only difference bewteen chunked and unchunked uncompression |
michael@0 | 461 | // is the flush mode we use: Z_SYNC_FLUSH (chunked) or Z_FINISH (unchunked). |
michael@0 | 462 | int ZLib::UncompressAtMostOrAll(Bytef *dest, uLongf *destLen, |
michael@0 | 463 | const Bytef *source, uLong *sourceLen, |
michael@0 | 464 | int flush_mode) { // Z_SYNC_FLUSH or Z_FINISH |
michael@0 | 465 | int err = Z_OK; |
michael@0 | 466 | |
michael@0 | 467 | if ( (err=UncompressInit(dest, destLen, source, sourceLen)) != Z_OK ) { |
michael@0 | 468 | LOG(WARNING) << "UncompressInit: Error: " << err << " SourceLen: " |
michael@0 | 469 | << *sourceLen; |
michael@0 | 470 | return err; |
michael@0 | 471 | } |
michael@0 | 472 | |
michael@0 | 473 | // This is used to figure out how many output bytes we wrote *this chunk*: |
michael@0 | 474 | const uLong old_total_out = uncomp_stream_.total_out; |
michael@0 | 475 | |
michael@0 | 476 | // This is used to figure out how many input bytes we read *this chunk*: |
michael@0 | 477 | const uLong old_total_in = uncomp_stream_.total_in; |
michael@0 | 478 | |
michael@0 | 479 | // Some setup happens only for the first chunk we compress in a run |
michael@0 | 480 | if ( first_chunk_ ) { |
michael@0 | 481 | first_chunk_ = false; // so we don't do this again |
michael@0 | 482 | |
michael@0 | 483 | // For the first chunk *only* (to avoid infinite troubles), we let |
michael@0 | 484 | // there be no actual data to uncompress. This sometimes triggers |
michael@0 | 485 | // when the input is only the gzip header, say. |
michael@0 | 486 | if ( *sourceLen == 0 ) { |
michael@0 | 487 | *destLen = 0; |
michael@0 | 488 | return Z_OK; |
michael@0 | 489 | } |
michael@0 | 490 | } |
michael@0 | 491 | |
michael@0 | 492 | // We'll uncompress as much as we can. If we end OK great, otherwise |
michael@0 | 493 | // if we get an error that seems to be the gzip footer, we store the |
michael@0 | 494 | // gzip footer and return OK, otherwise we return the error. |
michael@0 | 495 | |
michael@0 | 496 | // flush_mode is Z_SYNC_FLUSH for chunked mode, Z_FINISH for all mode. |
michael@0 | 497 | err = inflate(&uncomp_stream_, flush_mode); |
michael@0 | 498 | |
michael@0 | 499 | // Figure out how many bytes of the input zlib slurped up: |
michael@0 | 500 | const uLong bytes_read = uncomp_stream_.total_in - old_total_in; |
michael@0 | 501 | CHECK_LE(source + bytes_read, source + *sourceLen); |
michael@0 | 502 | *sourceLen = uncomp_stream_.avail_in; |
michael@0 | 503 | |
michael@0 | 504 | if ((err == Z_STREAM_END || err == Z_OK) // everything went ok |
michael@0 | 505 | && uncomp_stream_.avail_in == 0) { // and we read it all |
michael@0 | 506 | ; |
michael@0 | 507 | } else if (err == Z_STREAM_END && uncomp_stream_.avail_in > 0) { |
michael@0 | 508 | LOG(WARNING) |
michael@0 | 509 | << "UncompressChunkOrAll: Received some extra data, bytes total: " |
michael@0 | 510 | << uncomp_stream_.avail_in << " bytes: " |
michael@0 | 511 | << string(reinterpret_cast<const char *>(uncomp_stream_.next_in), |
michael@0 | 512 | min(int(uncomp_stream_.avail_in), 20)); |
michael@0 | 513 | UncompressErrorInit(); |
michael@0 | 514 | return Z_DATA_ERROR; // what's the extra data for? |
michael@0 | 515 | } else if (err != Z_OK && err != Z_STREAM_END && err != Z_BUF_ERROR) { |
michael@0 | 516 | // an error happened |
michael@0 | 517 | LOG(WARNING) << "UncompressChunkOrAll: Error: " << err |
michael@0 | 518 | << " avail_out: " << uncomp_stream_.avail_out; |
michael@0 | 519 | UncompressErrorInit(); |
michael@0 | 520 | return err; |
michael@0 | 521 | } else if (uncomp_stream_.avail_out == 0) { |
michael@0 | 522 | err = Z_BUF_ERROR; |
michael@0 | 523 | } |
michael@0 | 524 | |
michael@0 | 525 | assert(err == Z_OK || err == Z_BUF_ERROR || err == Z_STREAM_END); |
michael@0 | 526 | if (err == Z_STREAM_END) |
michael@0 | 527 | err = Z_OK; |
michael@0 | 528 | |
michael@0 | 529 | *destLen = uncomp_stream_.total_out - old_total_out; // size for this call |
michael@0 | 530 | |
michael@0 | 531 | return err; |
michael@0 | 532 | } |
michael@0 | 533 | |
michael@0 | 534 | int ZLib::UncompressChunkOrAll(Bytef *dest, uLongf *destLen, |
michael@0 | 535 | const Bytef *source, uLong sourceLen, |
michael@0 | 536 | int flush_mode) { // Z_SYNC_FLUSH or Z_FINISH |
michael@0 | 537 | const int ret = |
michael@0 | 538 | UncompressAtMostOrAll(dest, destLen, source, &sourceLen, flush_mode); |
michael@0 | 539 | if (ret == Z_BUF_ERROR) |
michael@0 | 540 | UncompressErrorInit(); |
michael@0 | 541 | return ret; |
michael@0 | 542 | } |
michael@0 | 543 | |
michael@0 | 544 | int ZLib::UncompressAtMost(Bytef *dest, uLongf *destLen, |
michael@0 | 545 | const Bytef *source, uLong *sourceLen) { |
michael@0 | 546 | return UncompressAtMostOrAll(dest, destLen, source, sourceLen, Z_SYNC_FLUSH); |
michael@0 | 547 | } |
michael@0 | 548 | |
michael@0 | 549 | // We make sure we've uncompressed everything, that is, the current |
michael@0 | 550 | // uncompress stream is at a compressed-buffer-EOF boundary. In gzip |
michael@0 | 551 | // mode, we also check the gzip footer to make sure we pass the gzip |
michael@0 | 552 | // consistency checks. We RETURN true iff both types of checks pass. |
michael@0 | 553 | bool ZLib::UncompressChunkDone() { |
michael@0 | 554 | assert(!first_chunk_ && uncomp_init_); |
michael@0 | 555 | // Make sure we're at the end-of-compressed-data point. This means |
michael@0 | 556 | // if we call inflate with Z_FINISH we won't consume any input or |
michael@0 | 557 | // write any output |
michael@0 | 558 | Bytef dummyin, dummyout; |
michael@0 | 559 | uLongf dummylen = 0; |
michael@0 | 560 | if ( UncompressChunkOrAll(&dummyout, &dummylen, &dummyin, 0, Z_FINISH) |
michael@0 | 561 | != Z_OK ) { |
michael@0 | 562 | return false; |
michael@0 | 563 | } |
michael@0 | 564 | |
michael@0 | 565 | // Make sure that when we exit, we can start a new round of chunks later |
michael@0 | 566 | Reset(); |
michael@0 | 567 | |
michael@0 | 568 | return true; |
michael@0 | 569 | } |
michael@0 | 570 | |
michael@0 | 571 | // Uncompresses the source buffer into the destination buffer. |
michael@0 | 572 | // The destination buffer must be long enough to hold the entire |
michael@0 | 573 | // decompressed contents. |
michael@0 | 574 | // |
michael@0 | 575 | // We only initialize the uncomp_stream once. Thereafter, we use |
michael@0 | 576 | // inflateReset, which should be faster. |
michael@0 | 577 | // |
michael@0 | 578 | // Returns Z_OK on success, otherwise, it returns a zlib error code. |
michael@0 | 579 | int ZLib::Uncompress(Bytef *dest, uLongf *destLen, |
michael@0 | 580 | const Bytef *source, uLong sourceLen) { |
michael@0 | 581 | int err; |
michael@0 | 582 | if ( (err=UncompressChunkOrAll(dest, destLen, source, sourceLen, |
michael@0 | 583 | Z_FINISH)) != Z_OK ) { |
michael@0 | 584 | Reset(); // let us try to compress again |
michael@0 | 585 | return err; |
michael@0 | 586 | } |
michael@0 | 587 | if ( !UncompressChunkDone() ) // calls Reset() |
michael@0 | 588 | return Z_DATA_ERROR; |
michael@0 | 589 | return Z_OK; // stream_end is ok |
michael@0 | 590 | } |
michael@0 | 591 | |
michael@0 | 592 | #endif // HAVE_LIBZ |
michael@0 | 593 | |
michael@0 | 594 | } // namespace snappy |