1 /****************************************************************************** 2 3 LZO1X-1 (Mini LZO) compressor/uncompressor 4 5 Copyright: 6 Copyright (c) 2009-2016 dunnhumby Germany GmbH. 7 All rights reserved. 8 9 License: 10 Boost Software License Version 1.0. See LICENSE_BOOST.txt for details. 11 Alternatively, this file may be distributed under the terms of the Tango 12 3-Clause BSD License (see LICENSE_BSD.txt for details). 13 14 ******************************************************************************/ 15 16 module ocean.io.compress.Lzo; 17 18 19 import ocean.io.compress.lzo.c.lzo1x: lzo1x_1_compress, 20 lzo1x_decompress, lzo1x_decompress_safe, 21 lzo1x_max_compressed_length, lzo_init, 22 Lzo1x1WorkmemSize, LzoStatus; 23 24 import ocean.io.compress.lzo.LzoCrc; 25 26 import ocean.io.compress.CompressException; 27 28 import ocean.core.Enforce: enforce; 29 30 import ocean.meta.types.Qualifiers; 31 32 import ocean.core.Verify; 33 34 version (unittest) import ocean.core.Test; 35 36 /****************************************************************************** 37 38 Lzo class 39 40 ******************************************************************************/ 41 42 class Lzo 43 { 44 alias LzoCrc.crc32 crc32; 45 46 /************************************************************************** 47 48 Working memory buffer for lzo1x_1_compress() 49 50 **************************************************************************/ 51 52 private void[] workmem; 53 54 /************************************************************************** 55 56 Static constructor 57 58 Throws: 59 CompressException if the library pouts 60 61 **************************************************************************/ 62 63 static this ( ) 64 { 65 enforce!(CompressException)(!lzo_init(), "Lzo kaputt"); 66 } 67 68 /************************************************************************** 69 70 Constructor 71 72 **************************************************************************/ 73 74 public this ( ) 75 { 76 this.workmem = new ubyte[Lzo1x1WorkmemSize]; 77 } 78 79 /************************************************************************** 80 81 Compresses src data. dst must have a length of at least 82 maxCompressedLength(src.length). 83 84 Params: 85 src = data to compress 86 dst = compressed data destination buffer 87 88 Returns: 89 length of compressed data in dst 90 91 Throws: 92 CompressionException on error 93 94 **************************************************************************/ 95 96 size_t compress ( in void[] src, void[] dst ) 97 { 98 verify(dst.length >= this.maxCompressedLength(src.length), 99 typeof (this).stringof ~ ".compress: dst buffer too short"); 100 101 size_t len; 102 103 this.checkStatus(lzo1x_1_compress(cast (const(ubyte)*) src.ptr, src.length, cast (ubyte*) dst.ptr, &len, this.workmem.ptr)); 104 105 return len; 106 } 107 108 /************************************************************************** 109 110 Uncompresses src data. dst must have at least the length of the 111 uncompressed data, which must be memorized at compression time. 112 113 Note: dst overflow checking is NOT done! 114 115 Params: 116 src = data to uncompress 117 dst = uncompressed data destination buffer 118 119 Returns: 120 length of uncompressed data in dst 121 122 Throws: 123 CompressionException on error 124 125 **************************************************************************/ 126 127 size_t uncompress ( in void[] src, void[] dst ) 128 { 129 size_t len; 130 131 this.checkStatus(lzo1x_decompress(cast (const(ubyte)*) src.ptr, src.length, cast (ubyte*) dst.ptr, &len)); 132 133 return len; 134 } 135 136 /************************************************************************** 137 138 Uncompresses src data, checking for dst not to overflow. 139 140 Params: 141 src = data to uncompress 142 dst = uncompressed data destination buffer 143 144 Returns: 145 length of uncompressed data in dst 146 147 Throws: 148 CompressionException on error 149 150 **************************************************************************/ 151 152 size_t decompressSafe ( in void[] src, void[] dst ) 153 { 154 size_t len = dst.length; 155 156 this.checkStatus(lzo1x_decompress_safe(cast (const(ubyte)*) src.ptr, src.length, cast (ubyte*) dst.ptr, &len)); 157 158 return len; 159 } 160 161 /****************************************************************************** 162 163 Calculates the maximum compressed length of data which has a length of 164 uncompressed_length. 165 166 Note: Surprisingly, this is more than uncompressed_length but that's the 167 worst case for completely uncompressable data. 168 169 Parameters: 170 uncompressed_length = length of data to compressed 171 172 Returns: 173 maximum compressed length of data 174 175 ******************************************************************************/ 176 177 static size_t maxCompressedLength ( size_t uncompressed_length ) 178 { 179 return lzo1x_max_compressed_length(uncompressed_length); 180 } 181 182 /************************************************************************** 183 184 Checks if status indicates an error. 185 186 Params: 187 status = LZO library function return status 188 189 Throws: 190 resulting 32-bit CRC value 191 192 **************************************************************************/ 193 194 static void checkStatus ( LzoStatus status ) 195 { 196 switch (status) 197 { 198 case LzoStatus.Error: 199 throw new CompressException(typeof (this).stringof ~ ": Error"); 200 201 case LzoStatus.OutOfMemory: 202 throw new CompressException(typeof (this).stringof ~ ": Out Of Memory"); 203 204 case LzoStatus.NotCompressible: 205 throw new CompressException(typeof (this).stringof ~ ": Not Compressible"); 206 207 case LzoStatus.InputOverrun: 208 throw new CompressException(typeof (this).stringof ~ ": Input Overrun"); 209 210 case LzoStatus.OutputOverrun: 211 throw new CompressException(typeof (this).stringof ~ ": Output Overrun"); 212 213 case LzoStatus.LookBehindOverrun: 214 throw new CompressException(typeof (this).stringof ~ ": Look Behind Overrun"); 215 216 case LzoStatus.EofNotFound: 217 throw new CompressException(typeof (this).stringof ~ ": Eof Not Found"); 218 219 case LzoStatus.InputNotConsumed: 220 throw new CompressException(typeof (this).stringof ~ ": Input Not Consumed"); 221 222 case LzoStatus.NotYetImplemented: 223 throw new CompressException(typeof (this).stringof ~ ": Not Yet Implemented"); 224 225 default: 226 return; 227 } 228 } 229 } 230 231 /// Simple compress / decompress example. 232 unittest 233 { 234 // LZO instance. (Usually a single one can be shared globally.) 235 auto lzo = new Lzo; 236 237 // Typical function to decompress a buffer that was received over the 238 // network. (i.e. where the buffer and the expected length of the 239 // decompressed data are both received from an external source.) 240 void decompress ( in void[] src, size_t expected_decompressed_length, 241 ref void[] dst ) 242 { 243 dst.length = expected_decompressed_length; 244 assumeSafeAppend(dst); 245 auto decompressed_length = lzo.decompressSafe(src, dst); 246 247 // Check that the length of the uncompressed data is what we expected. 248 // (It is important to verify this, when the data was received 249 // externally.) 250 enforce(decompressed_length == expected_decompressed_length); 251 } 252 253 auto original = "compress this!"; 254 255 // Compress. 256 auto compressed = new void[lzo.maxCompressedLength(original.length)]; 257 compressed.length = lzo.compress(original, compressed); 258 259 // Decompress. 260 void[] decompressed; 261 decompress(compressed, original.length, decompressed); 262 263 // We should have the original string back now. 264 test!("==")(decompressed, original); 265 } 266 267 /****************************************************************************** 268 269 Unit test 270 271 Add -debug=GcDisabled to the compiler command line to disable the garbage 272 collector. 273 274 Note: The unit test requires a file named "lzotest.dat" in the current 275 working directory at runtime. This file provides the data to be compressed 276 for testing and performance measurement. 277 278 ******************************************************************************/ 279 280 version (unittest): 281 282 // Uncomment the next line to see UnitTest output 283 // version = UnitTestVerbose; 284 285 import ocean.io.device.File; 286 import ocean.time.StopWatch; 287 288 debug (GcDisabled) import ocean.core.Memory; 289 290 import ocean.text.util.MetricPrefix; 291 292 import core.stdc.signal: signal, SIGINT; 293 294 /****************************************************************************** 295 296 Rounds x to the nearest integer value 297 298 Params: 299 x = value to round 300 301 Returns: 302 nearest integer value of x 303 304 ******************************************************************************/ 305 306 extern (C) int lrintf ( float x ); 307 308 /****************************************************************************** 309 310 Terminator structure 311 312 ******************************************************************************/ 313 314 struct Terminator 315 { 316 static: 317 318 /************************************************************************** 319 320 Termination flag 321 322 **************************************************************************/ 323 324 bool terminated = false; 325 326 /************************************************************************** 327 328 Signal handler; raises the termination flag 329 330 **************************************************************************/ 331 332 extern (C) void terminate ( int code ) nothrow @nogc 333 { 334 Terminator.terminated = true; 335 } 336 } 337 338 version (UnitTestVerbose) import ocean.io.Stdout; 339 340 unittest 341 { 342 debug (GcDisabled) 343 { 344 version (UnitTestVerbose) Stdout.formatln("LZO unittest: garbage collector disabled"); 345 GC.disable(); 346 } 347 348 StopWatch swatch; 349 350 MetricPrefix pre_comp_sz, pre_uncomp_sz, 351 pre_comp_tm, pre_uncomp_tm, pre_crc_tm; 352 353 version (UnitTestVerbose) Stdout.formatln("LZO unittest: loading test data from file \"lzotest.dat\""); 354 355 // FLAKEY: Avoid IO in unittests and specially fixed file names 356 File file; 357 358 try file = new File("lzotest.dat"); 359 catch (Exception e) 360 return; 361 362 scope data = new void[file.length]; 363 scope uncompressed = new void[file.length]; 364 scope compressed = new void[Lzo.maxCompressedLength(data.length)]; 365 scope lzo = new Lzo; 366 367 file.read(data); 368 369 file.close(); 370 371 version (UnitTestVerbose) Stdout.formatln("LZO unittest: loaded {} bytes of test data, compressing...", data.length); 372 373 swatch.start(); 374 375 compressed.length = lzo.compress(data, compressed); 376 377 ulong compr_us = swatch.microsec; 378 379 size_t uncompr_len = lzo.uncompress(compressed, uncompressed); 380 381 ulong uncomp_us = swatch.microsec; 382 383 uint crc32_data = lzo.crc32(data); 384 385 ulong crc_us = swatch.microsec; 386 387 test (uncompr_len == data.length, "uncompressed data length mismatch"); 388 test (uncompressed == data, "uncompressed data mismatch"); 389 test (lzo.crc32(uncompressed) == crc32_data, "uncompressed data CRC-32 mismatch"); 390 391 crc_us -= uncomp_us; 392 uncomp_us -= compr_us; 393 394 pre_comp_sz.bin(compressed.length); 395 pre_uncomp_sz.bin(uncompressed.length); 396 397 pre_comp_tm.dec(compr_us, -2); 398 pre_uncomp_tm.dec(uncomp_us, -2); 399 pre_crc_tm.dec(crc_us, -2); 400 401 version (UnitTestVerbose) 402 Stdout.formatln("LZO unittest results:\n\t" 403 ~ "uncompressed length: {} {}B\t({} bytes)\n\t" 404 ~ "compressed length: {} {}B\t({} bytes)\n\t" 405 ~ "compression ratio: {}%\n\t" 406 ~ "compression time: {} {}s\t({} µs)\n\t" 407 ~ "uncompression time: {} {}s\t({} µs)\n\t" 408 ~ "CRC-32 time: {} {}s\t({} µs)\n\t" 409 ~ "CRC-32 (uncompr'd): 0x{:X8}\n" 410 ~ "\n" 411 ~ "LZO unittest: Looping for memory leak detection; " 412 ~ "watch memory usage and press Ctrl+C to quit", 413 pre_uncomp_sz.scaled, pre_uncomp_sz.prefix, uncompressed.length, 414 pre_comp_sz.scaled, pre_comp_sz.prefix, compressed.length, 415 lrintf((compressed.length * 100.f) / uncompressed.length), 416 pre_comp_tm.scaled, pre_comp_tm.prefix, compr_us, 417 pre_uncomp_tm.scaled, pre_uncomp_tm.prefix, uncomp_us, 418 pre_crc_tm.scaled, pre_crc_tm.prefix, crc_us, 419 crc32_data); 420 421 auto prev_sigint_handler = signal(SIGINT, &Terminator.terminate); 422 423 scope (exit) signal(SIGINT, prev_sigint_handler); 424 425 while (!Terminator.terminated) 426 { 427 compressed.length = Lzo.maxCompressedLength(data.length); 428 429 compressed.length = lzo.compress(data, compressed); 430 431 lzo.uncompress(compressed, uncompressed); 432 } 433 }