#include "libdeflate_ext.h" #define DEFAULT_COMPRESSION 6 #define FORMAT_DEFLATE 0 #define FORMAT_ZLIB 1 #define FORMAT_GZIP 2 VALUE rb_eLibdefalteError, rb_eBadDataError; /* * call-seq: * Libdeflate.adler32(str = nil, adler = nil) -> integer * * Updates an Adler-32 checksum with str. If str is omitted, it * returns the initial value of Adler-32 checksum. If adler is omitted, * it assumes that the initial value of Adler-32 checksum is given. * * Libdeflate.adler32 #=> 1 * Libdeflate.adler32('foo') #=> 42074437 * Libdeflate.adler32('oo', Libdeflate.adler32('f')) #=> 42074437 */ static VALUE rb_libdeflate_adler32(int argc, VALUE *argv, VALUE self) { VALUE str, adler; unsigned long checksum; rb_scan_args(argc, argv, "02", &str, &adler); if (!NIL_P(adler)) { checksum = NUM2ULONG(adler); } else if (!NIL_P(str)) { checksum = libdeflate_adler32(0, NULL, 0); } else { checksum = 0; } if (NIL_P(str)) { checksum = libdeflate_adler32(checksum, NULL, 0); } else { StringValue(str); checksum = libdeflate_adler32(checksum, RSTRING_PTR(str), RSTRING_LEN(str)); } return ULONG2NUM(checksum); } /* * call-seq: * Libdeflate.crc32(str = nil, crc = nil) -> integer * * Updates a CRC-32 checksum with str. If str is omitted, it * returns the initial value of CRC-32 checksum. If crc is omitted, it * assumes that the initial value of CRC-32 checksum is given. * * Libdeflate.crc32 #=> 0 * Libdeflate.crc32('foo') #=> 2356372769 * Libdeflate.crc32('oo', Libdeflate.crc32('f')) #=> 2356372769 */ static VALUE rb_libdeflate_crc32(int argc, VALUE *argv, VALUE self) { VALUE str, crc; unsigned long checksum; rb_scan_args(argc, argv, "02", &str, &crc); if (!NIL_P(crc)) { checksum = NUM2ULONG(crc); } else if (!NIL_P(str)) { checksum = libdeflate_crc32(0, NULL, 0); } else { checksum = 0; } if (NIL_P(str)) { checksum = libdeflate_crc32(checksum, NULL, 0); } else { StringValue(str); checksum = libdeflate_crc32(checksum, RSTRING_PTR(str), RSTRING_LEN(str)); } return ULONG2NUM(checksum); } static void compressor_free(void *ptr) { libdeflate_free_compressor((struct libdeflate_compressor *)ptr); } static const rb_data_type_t compressor_data_type = { "compressor", { NULL, compressor_free, NULL, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY }; static VALUE rb_compressor_s_allocate(VALUE klass) { return TypedData_Wrap_Struct(klass, &compressor_data_type, 0); } /* * call-seq: * initialize(level = DEFAULT_COMPRESSION) -> compressor * * Returns a new Libdeflate::Compressor object. level must be in range * from 1 to 12, and defaults to DEFAULT_COMPRESSION. * * Libdeflate::Compressor.new #=> # */ static VALUE rb_compressor_initialize(int argc, VALUE *argv, VALUE self) { VALUE level; int compression_level; struct libdeflate_compressor *c; rb_scan_args(argc, argv, "01", &level); compression_level = NIL_P(level) ? DEFAULT_COMPRESSION : FIX2INT(level); c = libdeflate_alloc_compressor(compression_level); if (c == NULL) { rb_raise(rb_eLibdefalteError, "libdeflate_alloc_compressor: compression_level=%d", compression_level); } DATA_PTR(self) = c; return self; } static inline struct libdeflate_compressor * check_compressor(VALUE self) { return rb_check_typeddata(self, &compressor_data_type); } /* * call-seq: * compressor.compress(str, format = DEFLATE, outbuf = nil) -> string * * Compresses the given string into format. Valid values of format * are DEFLATE (default), ZLIB and GZIP. If outbuf is given, the * resulting compressed data will be written to it. * * compressor.compress('foo') #=> "\x01\x03\x00\xFC\xFFfoo" * compressor.compress('foo', Libdeflate::ZLIB) #=> "x\x9C\x01\x03\x00\xFC\xFFfoo\x02\x82\x01E" * * outbuf = 'bar' * compressor.compress('foo', nil, outbuf) #=> "\x01\x03\x00\xFC\xFFfoo" * outbuf #=> "\x01\x03\x00\xFC\xFFfoo" */ static VALUE rb_compressor_compress(int argc, VALUE *argv, VALUE self) { struct libdeflate_compressor *c = check_compressor(self); VALUE str, format, outbuf; size_t (*compress_func)(struct libdeflate_compressor *, const void *, size_t, void *, size_t); size_t (*compress_bound_func)(struct libdeflate_compressor *, size_t); size_t out_nbytes, max_out_nbytes; rb_scan_args(argc, argv, "12", &str, &format, &outbuf); StringValue(str); switch (NIL_P(format) ? FORMAT_DEFLATE : FIX2INT(format)) { case FORMAT_DEFLATE: compress_func = &libdeflate_deflate_compress; compress_bound_func = &libdeflate_deflate_compress_bound; break; case FORMAT_ZLIB: compress_func = &libdeflate_zlib_compress; compress_bound_func = &libdeflate_zlib_compress_bound; break; case FORMAT_GZIP: compress_func = &libdeflate_gzip_compress; compress_bound_func = &libdeflate_gzip_compress_bound; break; default: rb_raise(rb_eLibdefalteError, "unknown compressed data format: %d", FIX2INT(format)); } if (NIL_P(outbuf)) { outbuf = rb_str_buf_new(compress_bound_func(c, RSTRING_LEN(str))); } else { StringValue(outbuf); rb_str_modify(outbuf); } out_nbytes = compress_func(c, RSTRING_PTR(str), RSTRING_LEN(str), RSTRING_PTR(outbuf), rb_str_capacity(outbuf)); if (out_nbytes > 0) { rb_str_set_len(outbuf, out_nbytes); return outbuf; } max_out_nbytes = compress_bound_func(c, RSTRING_LEN(str)); if (rb_str_capacity(outbuf) >= max_out_nbytes) { rb_raise(rb_eLibdefalteError, "failed to compress data"); } rb_str_modify_expand(outbuf, max_out_nbytes - RSTRING_LEN(outbuf)); out_nbytes = compress_func(c, RSTRING_PTR(str), RSTRING_LEN(str), RSTRING_PTR(outbuf), rb_str_capacity(outbuf)); if (out_nbytes == 0) { rb_raise(rb_eLibdefalteError, "failed to compress data"); } rb_str_set_len(outbuf, out_nbytes); return outbuf; } static void decompressor_free(void *ptr) { libdeflate_free_decompressor((struct libdeflate_decompressor *)ptr); } static const rb_data_type_t decompressor_data_type = { "decompressor", { NULL, decompressor_free, NULL, }, 0, 0, RUBY_TYPED_FREE_IMMEDIATELY }; static VALUE rb_decompressor_s_allocate(VALUE klass) { return TypedData_Wrap_Struct(klass, &decompressor_data_type, 0); } /* * call-seq: * initialize -> decompressor * * Returns a new Libdeflate::Decompressor object. * * Libdeflate::Decompressor.new #=> # */ static VALUE rb_decompressor_initialize(VALUE self) { struct libdeflate_decompressor *d = libdeflate_alloc_decompressor(); if (d == NULL) { rb_raise(rb_eLibdefalteError, "libdeflate_alloc_decompressor"); } DATA_PTR(self) = d; return self; } static inline struct libdeflate_decompressor * check_decompressor(VALUE self) { return rb_check_typeddata(self, &decompressor_data_type); } static long next_power_of_two(long n) { n--; n |= n >> 1; n |= n >> 2; n |= n >> 4; n |= n >> 8; n |= n >> 16; #if LONG_MAX > UINT32_MAX n |= n >> 32; #endif n++; return n; } /* * call-seq: * decompressor.decompress(str, format = nil, outbuf = nil) -> string * * Decompresses the given string compressed in format. Valid values of * format are DEFLATE (default), ZLIB and GZIP. If outbuf is * given, the resulting uncompressed data will be written to it. * * decompressor.decompress("\x01\x03\x00\xFC\xFFfoo") * #=> "foo" * decompressor.decompress("x\x9C\x01\x03\x00\xFC\xFFfoo\x02\x82\x01E", Libdeflate::ZLIB) * #=> "foo" * * outbuf = 'bar' * decompressor.decompress("\x01\x03\x00\xFC\xFFfoo", nil, outbuf) #=> "foo" * outbuf #=> "foo" */ static VALUE rb_compressor_decompress(int argc, VALUE *argv, VALUE self) { struct libdeflate_decompressor *d = check_decompressor(self); VALUE str, format, outbuf; enum libdeflate_result (*decompress_func)(struct libdeflate_decompressor *, const void *, size_t, void *, size_t, size_t *); size_t actual_out_nbytes_ret; enum libdeflate_result decompress_result; rb_scan_args(argc, argv, "12", &str, &format, &outbuf); StringValue(str); switch (NIL_P(format) ? FORMAT_DEFLATE : FIX2INT(format)) { case FORMAT_DEFLATE: decompress_func = &libdeflate_deflate_decompress; break; case FORMAT_ZLIB: decompress_func = &libdeflate_zlib_decompress; break; case FORMAT_GZIP: decompress_func = &libdeflate_gzip_decompress; break; default: rb_raise(rb_eLibdefalteError, "unknown compressed data format: %d", FIX2INT(format)); } if (NIL_P(outbuf)) { outbuf = rb_str_buf_new(next_power_of_two(RSTRING_LEN(str)) << 4); } else { StringValue(outbuf); rb_str_modify(outbuf); } for (;;) { decompress_result = decompress_func(d, RSTRING_PTR(str), RSTRING_LEN(str), RSTRING_PTR(outbuf), rb_str_capacity(outbuf), &actual_out_nbytes_ret); if (decompress_result != LIBDEFLATE_INSUFFICIENT_SPACE) { break; } rb_str_modify_expand(outbuf, (rb_str_capacity(outbuf) << 1) - RSTRING_LEN(outbuf)); } if (decompress_result == LIBDEFLATE_BAD_DATA) { rb_raise(rb_eBadDataError, "failed to decompress data"); } else if (decompress_result != LIBDEFLATE_SUCCESS) { rb_raise(rb_eLibdefalteError, "failed to decompress data"); } rb_str_set_len(outbuf, actual_out_nbytes_ret); return outbuf; } void Init_libdeflate_ext(void) { VALUE rb_mLibdeflate, rb_cCompressor, rb_cDecompressor; rb_mLibdeflate = rb_define_module("Libdeflate"); rb_eLibdefalteError = rb_define_class_under(rb_mLibdeflate, "Error", rb_eStandardError); rb_eBadDataError = rb_define_class_under(rb_mLibdeflate, "BadDataError", rb_eLibdefalteError); /* * Default compression level which is a compromise between speed and compression ratio * * See Compressor#initialize. */ rb_define_const(rb_mLibdeflate, "DEFAULT_COMPRESSION", INT2FIX(DEFAULT_COMPRESSION)); /* * DEFLATE compressed data format * * See Compressor#compress and Decompressor#decompress. */ rb_define_const(rb_mLibdeflate, "DEFLATE", INT2FIX(FORMAT_DEFLATE)); /* * ZLIB compressed data format * * See Compressor#compress and Decompressor#decompress. */ rb_define_const(rb_mLibdeflate, "ZLIB", INT2FIX(FORMAT_ZLIB)); /* * GZIP compressed data format * * See Compressor#compress and Decompressor#decompress. */ rb_define_const(rb_mLibdeflate, "GZIP", INT2FIX(FORMAT_GZIP)); rb_define_module_function(rb_mLibdeflate, "adler32", rb_libdeflate_adler32, -1); rb_define_module_function(rb_mLibdeflate, "crc32", rb_libdeflate_crc32, -1); rb_cCompressor = rb_define_class_under(rb_mLibdeflate, "Compressor", rb_cObject); rb_define_alloc_func(rb_cCompressor, rb_compressor_s_allocate); rb_define_method(rb_cCompressor, "initialize", rb_compressor_initialize, -1); rb_define_method(rb_cCompressor, "compress", rb_compressor_compress, -1); rb_cDecompressor = rb_define_class_under(rb_mLibdeflate, "Decompressor", rb_cObject); rb_define_alloc_func(rb_cDecompressor, rb_decompressor_s_allocate); rb_define_method(rb_cDecompressor, "initialize", rb_decompressor_initialize, 0); rb_define_method(rb_cDecompressor, "decompress", rb_compressor_decompress, -1); }