libdeflate 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.gitmodules +3 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +1 -0
  6. data/.rubocop_todo.yml +9 -0
  7. data/.travis.yml +5 -0
  8. data/Gemfile +4 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +52 -0
  11. data/Rakefile +15 -0
  12. data/bin/console +14 -0
  13. data/bin/setup +8 -0
  14. data/ext/libdeflate/extconf.rb +14 -0
  15. data/ext/libdeflate/libdeflate/.gitignore +19 -0
  16. data/ext/libdeflate/libdeflate/COPYING +21 -0
  17. data/ext/libdeflate/libdeflate/Makefile +231 -0
  18. data/ext/libdeflate/libdeflate/Makefile.msc +64 -0
  19. data/ext/libdeflate/libdeflate/NEWS +57 -0
  20. data/ext/libdeflate/libdeflate/README.md +170 -0
  21. data/ext/libdeflate/libdeflate/common/common_defs.h +351 -0
  22. data/ext/libdeflate/libdeflate/common/compiler_gcc.h +134 -0
  23. data/ext/libdeflate/libdeflate/common/compiler_msc.h +95 -0
  24. data/ext/libdeflate/libdeflate/lib/adler32.c +213 -0
  25. data/ext/libdeflate/libdeflate/lib/adler32_impl.h +281 -0
  26. data/ext/libdeflate/libdeflate/lib/aligned_malloc.c +57 -0
  27. data/ext/libdeflate/libdeflate/lib/aligned_malloc.h +13 -0
  28. data/ext/libdeflate/libdeflate/lib/bt_matchfinder.h +357 -0
  29. data/ext/libdeflate/libdeflate/lib/crc32.c +368 -0
  30. data/ext/libdeflate/libdeflate/lib/crc32_impl.h +286 -0
  31. data/ext/libdeflate/libdeflate/lib/crc32_table.h +526 -0
  32. data/ext/libdeflate/libdeflate/lib/decompress_impl.h +404 -0
  33. data/ext/libdeflate/libdeflate/lib/deflate_compress.c +2817 -0
  34. data/ext/libdeflate/libdeflate/lib/deflate_compress.h +14 -0
  35. data/ext/libdeflate/libdeflate/lib/deflate_constants.h +66 -0
  36. data/ext/libdeflate/libdeflate/lib/deflate_decompress.c +889 -0
  37. data/ext/libdeflate/libdeflate/lib/gzip_compress.c +95 -0
  38. data/ext/libdeflate/libdeflate/lib/gzip_constants.h +45 -0
  39. data/ext/libdeflate/libdeflate/lib/gzip_decompress.c +130 -0
  40. data/ext/libdeflate/libdeflate/lib/hc_matchfinder.h +405 -0
  41. data/ext/libdeflate/libdeflate/lib/lib_common.h +35 -0
  42. data/ext/libdeflate/libdeflate/lib/matchfinder_avx2.h +53 -0
  43. data/ext/libdeflate/libdeflate/lib/matchfinder_common.h +205 -0
  44. data/ext/libdeflate/libdeflate/lib/matchfinder_neon.h +61 -0
  45. data/ext/libdeflate/libdeflate/lib/matchfinder_sse2.h +53 -0
  46. data/ext/libdeflate/libdeflate/lib/unaligned.h +202 -0
  47. data/ext/libdeflate/libdeflate/lib/x86_cpu_features.c +169 -0
  48. data/ext/libdeflate/libdeflate/lib/x86_cpu_features.h +48 -0
  49. data/ext/libdeflate/libdeflate/lib/zlib_compress.c +87 -0
  50. data/ext/libdeflate/libdeflate/lib/zlib_constants.h +21 -0
  51. data/ext/libdeflate/libdeflate/lib/zlib_decompress.c +91 -0
  52. data/ext/libdeflate/libdeflate/libdeflate.h +274 -0
  53. data/ext/libdeflate/libdeflate/programs/benchmark.c +558 -0
  54. data/ext/libdeflate/libdeflate/programs/checksum.c +197 -0
  55. data/ext/libdeflate/libdeflate/programs/detect.sh +62 -0
  56. data/ext/libdeflate/libdeflate/programs/gzip.c +603 -0
  57. data/ext/libdeflate/libdeflate/programs/prog_util.c +530 -0
  58. data/ext/libdeflate/libdeflate/programs/prog_util.h +162 -0
  59. data/ext/libdeflate/libdeflate/programs/test_checksums.c +135 -0
  60. data/ext/libdeflate/libdeflate/programs/tgetopt.c +118 -0
  61. data/ext/libdeflate/libdeflate/tools/afl-fuzz/Makefile +12 -0
  62. data/ext/libdeflate/libdeflate/tools/afl-fuzz/deflate_compress/fuzz.c +40 -0
  63. data/ext/libdeflate/libdeflate/tools/afl-fuzz/deflate_compress/inputs/0 +0 -0
  64. data/ext/libdeflate/libdeflate/tools/afl-fuzz/deflate_decompress/fuzz.c +28 -0
  65. data/ext/libdeflate/libdeflate/tools/afl-fuzz/deflate_decompress/inputs/0 +3 -0
  66. data/ext/libdeflate/libdeflate/tools/afl-fuzz/gzip_decompress/fuzz.c +28 -0
  67. data/ext/libdeflate/libdeflate/tools/afl-fuzz/gzip_decompress/inputs/0 +0 -0
  68. data/ext/libdeflate/libdeflate/tools/afl-fuzz/prepare_for_fuzz.sh +14 -0
  69. data/ext/libdeflate/libdeflate/tools/afl-fuzz/zlib_decompress/fuzz.c +28 -0
  70. data/ext/libdeflate/libdeflate/tools/afl-fuzz/zlib_decompress/inputs/0 +3 -0
  71. data/ext/libdeflate/libdeflate/tools/android_build.sh +104 -0
  72. data/ext/libdeflate/libdeflate/tools/checksum_benchmarks.sh +76 -0
  73. data/ext/libdeflate/libdeflate/tools/exec_tests.sh +30 -0
  74. data/ext/libdeflate/libdeflate/tools/gen_crc32_multipliers.c +108 -0
  75. data/ext/libdeflate/libdeflate/tools/gen_crc32_table.c +100 -0
  76. data/ext/libdeflate/libdeflate/tools/gzip_tests.sh +412 -0
  77. data/ext/libdeflate/libdeflate/tools/make-windows-releases +21 -0
  78. data/ext/libdeflate/libdeflate/tools/mips_build.sh +9 -0
  79. data/ext/libdeflate/libdeflate/tools/msc_test.bat +3 -0
  80. data/ext/libdeflate/libdeflate/tools/pgo_build.sh +23 -0
  81. data/ext/libdeflate/libdeflate/tools/produce_gzip_benchmark_table.sh +37 -0
  82. data/ext/libdeflate/libdeflate/tools/run_tests.sh +305 -0
  83. data/ext/libdeflate/libdeflate/tools/windows_build.sh +10 -0
  84. data/ext/libdeflate/libdeflate_ext.c +389 -0
  85. data/ext/libdeflate/libdeflate_ext.h +8 -0
  86. data/lib/libdeflate.rb +2 -0
  87. data/lib/libdeflate/version.rb +3 -0
  88. data/libdeflate.gemspec +33 -0
  89. metadata +230 -0
@@ -0,0 +1,23 @@
1
+ #!/bin/bash
2
+
3
+ # Try gcc profile-guided optimizations
4
+
5
+ set -eu
6
+
7
+ MAKE="make -j$(grep -c processor /proc/cpuinfo)"
8
+ DATAFILE="$HOME/data/silesia"
9
+
10
+ $MAKE benchmark > /dev/null
11
+ echo "====================="
12
+ echo "Original performance:"
13
+ echo "---------------------"
14
+ ./benchmark "$@" "$DATAFILE"
15
+
16
+ $MAKE CFLAGS=-fprofile-generate LDFLAGS=-fprofile-generate benchmark > /dev/null
17
+ ./benchmark "$@" "$DATAFILE" > /dev/null
18
+ $MAKE CFLAGS=-fprofile-use benchmark > /dev/null
19
+ rm -f {lib,programs}/*.gcda
20
+ echo "=========================="
21
+ echo "PGO-optimized performance:"
22
+ echo "--------------------------"
23
+ ./benchmark "$@" "$DATAFILE"
@@ -0,0 +1,37 @@
1
+ #!/bin/bash
2
+
3
+ set -e
4
+
5
+ do_benchmark() {
6
+ usize=$(stat -c %s "$file")
7
+ "$HOME/proj/libdeflate/benchmark" -g -s $usize "$@" "$file" \
8
+ | grep Compressed | cut -f 4 -d ' '
9
+ }
10
+
11
+ echo "File | zlib -6 | zlib -9 | libdeflate -6 | libdeflate -9 | libdeflate -12"
12
+ echo "-----|---------|---------|---------------|---------------|---------------"
13
+
14
+ for file in "$@"; do
15
+ echo -n "$(basename "$file")"
16
+ results=()
17
+ results+=($(do_benchmark -Y -6))
18
+ results+=($(do_benchmark -Y -9))
19
+ results+=($(do_benchmark -6))
20
+ results+=($(do_benchmark -9))
21
+ results+=($(do_benchmark -12))
22
+ best=2000000000
23
+ for result in "${results[@]}"; do
24
+ if (( result < best)); then
25
+ best=$result
26
+ fi
27
+ done
28
+ for result in "${results[@]}"; do
29
+ if (( result == best )); then
30
+ em="**"
31
+ else
32
+ em=""
33
+ fi
34
+ echo -n " | ${em}${result}${em}"
35
+ done
36
+ echo
37
+ done
@@ -0,0 +1,305 @@
1
+ #!/bin/bash
2
+ #
3
+ # Test script for libdeflate
4
+ #
5
+ # Usage: ./tools/run_tests.sh [TESTGROUP]... [-TESTGROUP]...
6
+ #
7
+ # By default all tests are run, but it is possible to explicitly include or
8
+ # exclude specific test groups.
9
+ #
10
+
11
+ set -eu -o pipefail
12
+ cd "$(dirname "$0")/.."
13
+
14
+ TESTGROUPS=(all)
15
+
16
+ set_test_groups() {
17
+ TESTGROUPS=("$@")
18
+ local have_exclusion=0
19
+ local have_all=0
20
+ for group in "${TESTGROUPS[@]}"; do
21
+ if [[ $group == -* ]]; then
22
+ have_exclusion=1
23
+ elif [[ $group == all ]]; then
24
+ have_all=1
25
+ fi
26
+ done
27
+ if (( have_exclusion && !have_all )); then
28
+ TESTGROUPS=(all "${TESTGROUPS[@]}")
29
+ fi
30
+ }
31
+
32
+ if [ $# -gt 0 ]; then
33
+ set_test_groups "$@"
34
+ fi
35
+
36
+ SMOKEDATA="${SMOKEDATA:=$HOME/data/smokedata}"
37
+ if [ ! -e "$SMOKEDATA" ]; then
38
+ echo "SMOKEDATA (value: $SMOKEDATA) does not exist. Set the" \
39
+ "environmental variable SMOKEDATA to a file to use in" \
40
+ "compression/decompression tests." 1>&2
41
+ exit 1
42
+ fi
43
+
44
+ NDKDIR="${NDKDIR:=/opt/android-ndk}"
45
+
46
+ FILES=("$SMOKEDATA" ./tools/exec_tests.sh benchmark test_checksums)
47
+ EXEC_TESTS_CMD="WRAPPER= SMOKEDATA=\"$(basename $SMOKEDATA)\" sh exec_tests.sh"
48
+ NPROC=$(grep -c processor /proc/cpuinfo)
49
+ VALGRIND="valgrind --quiet --error-exitcode=100 --leak-check=full --errors-for-leak-kinds=all"
50
+ SANITIZE_CFLAGS="-fsanitize=undefined -fno-sanitize-recover=undefined,integer"
51
+
52
+ TMPFILE="$(mktemp)"
53
+ trap "rm -f \"$TMPFILE\"" EXIT
54
+
55
+ ###############################################################################
56
+
57
+ rm -f run_tests.log
58
+ exec > >(tee -ia run_tests.log)
59
+ exec 2> >(tee -ia run_tests.log >&2)
60
+
61
+ TESTS_SKIPPED=0
62
+ log_skip() {
63
+ log "[WARNING, TEST SKIPPED]: $@"
64
+ TESTS_SKIPPED=1
65
+ }
66
+
67
+ log() {
68
+ echo "[$(date)] $@"
69
+ }
70
+
71
+ run_cmd() {
72
+ log "$@"
73
+ "$@" > /dev/null
74
+ }
75
+
76
+ test_group_included() {
77
+ local included=0 group
78
+ for group in "${TESTGROUPS[@]}"; do
79
+ if [ "$group" = "$1" ]; then
80
+ included=1 # explicitly included
81
+ break
82
+ fi
83
+ if [ "$group" = "-$1" ]; then
84
+ included=0 # explicitly excluded
85
+ break
86
+ fi
87
+ if [ "$group" = "all" ]; then # implicitly included
88
+ included=1
89
+ fi
90
+ done
91
+ if (( included )); then
92
+ log "Starting test group: $1"
93
+ fi
94
+ (( included ))
95
+ }
96
+
97
+ ###############################################################################
98
+
99
+ native_build_and_test() {
100
+ make "$@" -j$NPROC all test_programs > /dev/null
101
+ WRAPPER="$WRAPPER" SMOKEDATA="$SMOKEDATA" sh ./tools/exec_tests.sh \
102
+ > /dev/null
103
+ }
104
+
105
+ native_tests() {
106
+ test_group_included native || return 0
107
+ local compiler cflags compilers=(gcc)
108
+ shopt -s nullglob
109
+ compilers+=(/usr/bin/gcc-[0-9]*)
110
+ compilers+=(/usr/bin/clang-[0-9]*)
111
+ compilers+=(/opt/gcc*/bin/gcc)
112
+ compilers+=(/opt/clang*/bin/clang)
113
+ shopt -u nullglob
114
+ for compiler in ${compilers[@]}; do
115
+ for cflags in "" "-march=native" "-m32"; do
116
+ if [ "$compiler" = "/usr/bin/gcc-4.8" -a \
117
+ "$cflags" = "-m32" ]; then
118
+ continue
119
+ fi
120
+ log "Running tests with CC=$compiler," \
121
+ "CFLAGS=$cflags"
122
+ WRAPPER= native_build_and_test \
123
+ CC=$compiler CFLAGS="$cflags -Werror"
124
+ done
125
+ done
126
+
127
+ log "Running tests with Valgrind"
128
+ WRAPPER="$VALGRIND" native_build_and_test
129
+
130
+ log "Running tests with undefined behavior sanitizer"
131
+ WRAPPER= native_build_and_test CC=clang CFLAGS="$SANITIZE_CFLAGS"
132
+ }
133
+
134
+ ###############################################################################
135
+
136
+ android_build() {
137
+ run_cmd ./tools/android_build.sh --ndkdir="$NDKDIR" "$@"
138
+ }
139
+
140
+ android_build_and_test() {
141
+ android_build "$@"
142
+ run_cmd adb push "${FILES[@]}" /data/local/tmp/
143
+
144
+ # Note: adb shell always returns 0, even if the shell command fails...
145
+ log "adb shell \"cd /data/local/tmp && $EXEC_TESTS_CMD\""
146
+ adb shell "cd /data/local/tmp && $EXEC_TESTS_CMD" > "$TMPFILE"
147
+ if ! grep -q "exec_tests finished successfully" "$TMPFILE"; then
148
+ log "Android test failure! adb shell output:"
149
+ cat "$TMPFILE"
150
+ return 1
151
+ fi
152
+ }
153
+
154
+ android_tests() {
155
+ local compiler
156
+
157
+ test_group_included android || return 0
158
+ if [ ! -e $NDKDIR ]; then
159
+ log_skip "Android NDK was not found in NDKDIR=$NDKDIR!" \
160
+ "If you want to run the Android tests, set the" \
161
+ "environmental variable NDKDIR to the location of" \
162
+ "your Android NDK installation"
163
+ return 0
164
+ fi
165
+
166
+ if ! type -P adb > /dev/null; then
167
+ log_skip "adb (android-tools) is not installed"
168
+ return 0
169
+ fi
170
+
171
+ if ! adb devices | grep -q 'device$'; then
172
+ log_skip "No Android device is currently attached"
173
+ return 0
174
+ fi
175
+
176
+ for compiler in gcc clang; do
177
+ android_build_and_test --arch=arm --compiler=$compiler
178
+
179
+ android_build_and_test --arch=arm --compiler=$compiler \
180
+ --disable-neon
181
+
182
+ # arm64: currently compiled but not run
183
+ android_build --arch=arm64 --compiler=$compiler
184
+ done
185
+ }
186
+
187
+ ###############################################################################
188
+
189
+ mips_tests() {
190
+ test_group_included mips || return 0
191
+ if ! ping -c 1 dd-wrt > /dev/null; then
192
+ log_skip "Can't run MIPS tests: dd-wrt system not available"
193
+ return 0
194
+ fi
195
+ run_cmd ./tools/mips_build.sh
196
+ run_cmd scp "${FILES[@]}" root@dd-wrt:
197
+ run_cmd ssh root@dd-wrt "$EXEC_TESTS_CMD"
198
+ }
199
+
200
+ ###############################################################################
201
+
202
+ windows_tests() {
203
+ local arch
204
+
205
+ test_group_included windows || return 0
206
+
207
+ # Windows: currently compiled but not run
208
+ for arch in i686 x86_64; do
209
+ local compiler=${arch}-w64-mingw32-gcc
210
+ if ! type -P $compiler > /dev/null; then
211
+ log_skip "$compiler not found"
212
+ continue
213
+ fi
214
+ run_cmd make CC=$compiler CFLAGS=-Werror -j$NPROC \
215
+ all test_programs
216
+ done
217
+ }
218
+
219
+ ###############################################################################
220
+
221
+ static_analysis_tests() {
222
+ test_group_included static_analysis || return 0
223
+ if ! type -P scan-build > /dev/null; then
224
+ log_skip "clang static analyzer (scan-build) not found"
225
+ return 0
226
+ fi
227
+ run_cmd scan-build --status-bugs make -j$NPROC all test_programs
228
+ }
229
+
230
+ ###############################################################################
231
+
232
+ gzip_tests() {
233
+ test_group_included gzip || return 0
234
+
235
+ local gzip gunzip
236
+ run_cmd make -j$NPROC gzip gunzip
237
+ for gzip in "$PWD/gzip" /usr/bin/gzip; do
238
+ for gunzip in "$PWD/gunzip" /usr/bin/gunzip; do
239
+ log "Running gzip program tests with GZIP=$gzip," \
240
+ "GUNZIP=$gunzip"
241
+ GZIP="$gzip" GUNZIP="$gunzip" SMOKEDATA="$SMOKEDATA" \
242
+ ./tools/gzip_tests.sh
243
+ done
244
+ done
245
+
246
+ log "Running gzip program tests with Valgrind"
247
+ GZIP="$VALGRIND $PWD/gzip" GUNZIP="$VALGRIND $PWD/gunzip" \
248
+ SMOKEDATA="$SMOKEDATA" ./tools/gzip_tests.sh
249
+
250
+ log "Running gzip program tests with undefined behavior sanitizer"
251
+ run_cmd make -j$NPROC CC=clang CFLAGS="$SANITIZE_CFLAGS" gzip gunzip
252
+ GZIP="$PWD/gzip" GUNZIP="$PWD/gunzip" \
253
+ SMOKEDATA="$SMOKEDATA" ./tools/gzip_tests.sh
254
+ }
255
+
256
+ ###############################################################################
257
+
258
+ edge_case_tests() {
259
+ test_group_included edge_case || return 0
260
+
261
+ # Regression test for "deflate_compress: fix corruption with long
262
+ # literal run". Try to compress a file longer than 65535 bytes where no
263
+ # 2-byte sequence (3 would be sufficient) is repeated <= 32768 bytes
264
+ # apart, and the distribution of bytes remains constant throughout, and
265
+ # yet not all bytes are used so the data is still slightly compressible.
266
+ # There will be no matches in this data, but the compressor should still
267
+ # output a compressed block, and this block should contain more than
268
+ # 65535 consecutive literals, which triggered the bug.
269
+ #
270
+ # Note: on random data, this situation is extremely unlikely if the
271
+ # compressor uses all matches it finds, since random data will on
272
+ # average have a 3-byte match every (256**3)/32768 = 512 bytes.
273
+ python3 > "$TMPFILE" << EOF
274
+ import sys
275
+ for i in range(2):
276
+ for stride in range(1,251):
277
+ b = bytes(stride*multiple % 251 for multiple in range(251))
278
+ sys.stdout.buffer.write(b)
279
+ EOF
280
+ run_cmd make -j$NPROC benchmark
281
+ run_cmd ./benchmark -3 "$TMPFILE"
282
+ run_cmd ./benchmark -6 "$TMPFILE"
283
+ run_cmd ./benchmark -12 "$TMPFILE"
284
+ }
285
+
286
+ ###############################################################################
287
+
288
+ log "Starting libdeflate tests"
289
+ log " TESTGROUPS=(${TESTGROUPS[@]})"
290
+ log " SMOKEDATA=$SMOKEDATA"
291
+ log " NDKDIR=$NDKDIR"
292
+
293
+ native_tests
294
+ android_tests
295
+ mips_tests
296
+ windows_tests
297
+ static_analysis_tests
298
+ gzip_tests
299
+ edge_case_tests
300
+
301
+ if (( TESTS_SKIPPED )); then
302
+ log "No tests failed, but some tests were skipped. See above."
303
+ else
304
+ log "All tests passed!"
305
+ fi
@@ -0,0 +1,10 @@
1
+ #!/bin/bash
2
+
3
+ set -eu
4
+
5
+ make -j CC=i686-w64-mingw32-gcc all test_programs
6
+ cp -vf *.exe /j/exe/
7
+ make -j CC=x86_64-w64-mingw32-gcc all test_programs
8
+ cp -vf *.exe /j/exe64/
9
+
10
+ sudo $HOME/bin/sudo/restart-smbd
@@ -0,0 +1,389 @@
1
+ #include "libdeflate_ext.h"
2
+
3
+ #define DEFAULT_COMPRESSION 6
4
+
5
+ #define FORMAT_DEFLATE 0
6
+ #define FORMAT_ZLIB 1
7
+ #define FORMAT_GZIP 2
8
+
9
+ VALUE rb_eLibdefalteError, rb_eBadDataError;
10
+
11
+ /*
12
+ * call-seq:
13
+ * Libdeflate.adler32(str = nil, adler = nil) -> integer
14
+ *
15
+ * Updates an Adler-32 checksum with <i>str</i>. If <i>str</i> is omitted, it
16
+ * returns the initial value of Adler-32 checksum. If <i>adler</i> is omitted,
17
+ * it assumes that the initial value of Adler-32 checksum is given.
18
+ *
19
+ * Libdeflate.adler32 #=> 1
20
+ * Libdeflate.adler32('foo') #=> 42074437
21
+ * Libdeflate.adler32('oo', Libdeflate.adler32('f')) #=> 42074437
22
+ */
23
+ static VALUE
24
+ rb_libdeflate_adler32(int argc, VALUE *argv, VALUE self)
25
+ {
26
+ VALUE str, adler;
27
+ unsigned long checksum;
28
+
29
+ rb_scan_args(argc, argv, "02", &str, &adler);
30
+
31
+ if (!NIL_P(adler)) {
32
+ checksum = NUM2ULONG(adler);
33
+ } else if (!NIL_P(str)) {
34
+ checksum = libdeflate_adler32(0, NULL, 0);
35
+ } else {
36
+ checksum = 0;
37
+ }
38
+
39
+ if (NIL_P(str)) {
40
+ checksum = libdeflate_adler32(checksum, NULL, 0);
41
+ } else {
42
+ StringValue(str);
43
+ checksum = libdeflate_adler32(checksum, RSTRING_PTR(str), RSTRING_LEN(str));
44
+ }
45
+
46
+ return ULONG2NUM(checksum);
47
+ }
48
+
49
+ /*
50
+ * call-seq:
51
+ * Libdeflate.crc32(str = nil, crc = nil) -> integer
52
+ *
53
+ * Updates a CRC-32 checksum with <i>str</i>. If <i>str</i> is omitted, it
54
+ * returns the initial value of CRC-32 checksum. If <i>crc</i> is omitted, it
55
+ * assumes that the initial value of CRC-32 checksum is given.
56
+ *
57
+ * Libdeflate.crc32 #=> 0
58
+ * Libdeflate.crc32('foo') #=> 2356372769
59
+ * Libdeflate.crc32('oo', Libdeflate.crc32('f')) #=> 2356372769
60
+ */
61
+ static VALUE
62
+ rb_libdeflate_crc32(int argc, VALUE *argv, VALUE self)
63
+ {
64
+ VALUE str, crc;
65
+ unsigned long checksum;
66
+
67
+ rb_scan_args(argc, argv, "02", &str, &crc);
68
+
69
+ if (!NIL_P(crc)) {
70
+ checksum = NUM2ULONG(crc);
71
+ } else if (!NIL_P(str)) {
72
+ checksum = libdeflate_crc32(0, NULL, 0);
73
+ } else {
74
+ checksum = 0;
75
+ }
76
+
77
+ if (NIL_P(str)) {
78
+ checksum = libdeflate_crc32(checksum, NULL, 0);
79
+ } else {
80
+ StringValue(str);
81
+ checksum = libdeflate_crc32(checksum, RSTRING_PTR(str), RSTRING_LEN(str));
82
+ }
83
+
84
+ return ULONG2NUM(checksum);
85
+ }
86
+
87
+ static void
88
+ compressor_free(void *ptr)
89
+ {
90
+ libdeflate_free_compressor((struct libdeflate_compressor *)ptr);
91
+ }
92
+
93
+ static const rb_data_type_t compressor_data_type = {
94
+ "compressor",
95
+ { NULL, compressor_free, NULL, },
96
+ 0, 0, RUBY_TYPED_FREE_IMMEDIATELY
97
+ };
98
+
99
+ static VALUE
100
+ rb_compressor_s_allocate(VALUE klass)
101
+ {
102
+ return TypedData_Wrap_Struct(klass, &compressor_data_type, 0);
103
+ }
104
+
105
+ /*
106
+ * call-seq:
107
+ * initialize(level = DEFAULT_COMPRESSION) -> compressor
108
+ *
109
+ * Returns a new Libdeflate::Compressor object. <i>level</i> must be in range
110
+ * from 1 to 12, and defaults to DEFAULT_COMPRESSION.
111
+ *
112
+ * Libdeflate::Compressor.new #=> #<Libdeflate::Compressor:0x007fad31b672c8>
113
+ */
114
+ static VALUE
115
+ rb_compressor_initialize(int argc, VALUE *argv, VALUE self)
116
+ {
117
+ VALUE level;
118
+ int compression_level;
119
+ struct libdeflate_compressor *c;
120
+
121
+ rb_scan_args(argc, argv, "01", &level);
122
+
123
+ compression_level = NIL_P(level) ? DEFAULT_COMPRESSION : FIX2INT(level);
124
+
125
+ c = libdeflate_alloc_compressor(compression_level);
126
+ if (c == NULL) {
127
+ rb_raise(rb_eLibdefalteError, "libdeflate_alloc_compressor: compression_level=%d", compression_level);
128
+ }
129
+
130
+ DATA_PTR(self) = c;
131
+
132
+ return self;
133
+ }
134
+
135
+ static inline struct libdeflate_compressor *
136
+ check_compressor(VALUE self)
137
+ {
138
+ return rb_check_typeddata(self, &compressor_data_type);
139
+ }
140
+
141
+ /*
142
+ * call-seq:
143
+ * compressor.compress(str, format = DEFLATE, outbuf = nil) -> string
144
+ *
145
+ * Compresses the given string into <i>format</i>. Valid values of <i>format</i>
146
+ * are DEFLATE (default), ZLIB and GZIP. If <i>outbuf</i> is given, the
147
+ * resulting compressed data will be written to it.
148
+ *
149
+ * compressor.compress('foo') #=> "\x01\x03\x00\xFC\xFFfoo"
150
+ * compressor.compress('foo', Libdeflate::ZLIB) #=> "x\x9C\x01\x03\x00\xFC\xFFfoo\x02\x82\x01E"
151
+ *
152
+ * outbuf = 'bar'
153
+ * compressor.compress('foo', nil, outbuf) #=> "\x01\x03\x00\xFC\xFFfoo"
154
+ * outbuf #=> "\x01\x03\x00\xFC\xFFfoo"
155
+ */
156
+ static VALUE
157
+ rb_compressor_compress(int argc, VALUE *argv, VALUE self)
158
+ {
159
+ struct libdeflate_compressor *c = check_compressor(self);
160
+ VALUE str, format, outbuf;
161
+ size_t (*compress_func)(struct libdeflate_compressor *, const void *, size_t, void *, size_t);
162
+ size_t (*compress_bound_func)(struct libdeflate_compressor *, size_t);
163
+ size_t out_nbytes, max_out_nbytes;
164
+
165
+ rb_scan_args(argc, argv, "12", &str, &format, &outbuf);
166
+
167
+ StringValue(str);
168
+
169
+ switch (NIL_P(format) ? FORMAT_DEFLATE : FIX2INT(format)) {
170
+ case FORMAT_DEFLATE:
171
+ compress_func = &libdeflate_deflate_compress;
172
+ compress_bound_func = &libdeflate_deflate_compress_bound;
173
+ break;
174
+ case FORMAT_ZLIB:
175
+ compress_func = &libdeflate_zlib_compress;
176
+ compress_bound_func = &libdeflate_zlib_compress_bound;
177
+ break;
178
+ case FORMAT_GZIP:
179
+ compress_func = &libdeflate_gzip_compress;
180
+ compress_bound_func = &libdeflate_gzip_compress_bound;
181
+ break;
182
+ default:
183
+ rb_raise(rb_eLibdefalteError, "unknown compressed data format: %d", FIX2INT(format));
184
+ }
185
+
186
+ if (NIL_P(outbuf)) {
187
+ outbuf = rb_str_buf_new(compress_bound_func(c, RSTRING_LEN(str)));
188
+ } else {
189
+ StringValue(outbuf);
190
+ rb_str_modify(outbuf);
191
+ }
192
+
193
+ out_nbytes = compress_func(c,
194
+ RSTRING_PTR(str),
195
+ RSTRING_LEN(str),
196
+ RSTRING_PTR(outbuf),
197
+ rb_str_capacity(outbuf));
198
+
199
+ if (out_nbytes > 0) {
200
+ rb_str_set_len(outbuf, out_nbytes);
201
+ OBJ_INFECT(outbuf, str);
202
+ return outbuf;
203
+ }
204
+
205
+ max_out_nbytes = compress_bound_func(c, RSTRING_LEN(str));
206
+ if (rb_str_capacity(outbuf) >= max_out_nbytes) {
207
+ rb_raise(rb_eLibdefalteError, "failed to compress data");
208
+ }
209
+
210
+ rb_str_modify_expand(outbuf, max_out_nbytes - RSTRING_LEN(outbuf));
211
+
212
+ out_nbytes = compress_func(c,
213
+ RSTRING_PTR(str),
214
+ RSTRING_LEN(str),
215
+ RSTRING_PTR(outbuf),
216
+ rb_str_capacity(outbuf));
217
+
218
+ if (out_nbytes == 0) {
219
+ rb_raise(rb_eLibdefalteError, "failed to compress data");
220
+ }
221
+
222
+ rb_str_set_len(outbuf, out_nbytes);
223
+ OBJ_INFECT(outbuf, str);
224
+
225
+ return outbuf;
226
+ }
227
+
228
+ static void
229
+ decompressor_free(void *ptr)
230
+ {
231
+ libdeflate_free_decompressor((struct libdeflate_decompressor *)ptr);
232
+ }
233
+
234
+ static const rb_data_type_t decompressor_data_type = {
235
+ "decompressor",
236
+ { NULL, decompressor_free, NULL, },
237
+ 0, 0, RUBY_TYPED_FREE_IMMEDIATELY
238
+ };
239
+
240
+ static VALUE
241
+ rb_decompressor_s_allocate(VALUE klass)
242
+ {
243
+ return TypedData_Wrap_Struct(klass, &decompressor_data_type, 0);
244
+ }
245
+
246
+ /*
247
+ * call-seq:
248
+ * initialize -> decompressor
249
+ *
250
+ * Returns a new Libdeflate::Decompressor object.
251
+ *
252
+ * Libdeflate::Decompressor.new #=> #<Libdeflate::Decompressor:0x007fde591a6500>
253
+ */
254
+ static VALUE
255
+ rb_decompressor_initialize(VALUE self)
256
+ {
257
+ struct libdeflate_decompressor *d = libdeflate_alloc_decompressor();
258
+ if (d == NULL) {
259
+ rb_raise(rb_eLibdefalteError, "libdeflate_alloc_decompressor");
260
+ }
261
+
262
+ DATA_PTR(self) = d;
263
+
264
+ return self;
265
+ }
266
+
267
+ static inline struct libdeflate_decompressor *
268
+ check_decompressor(VALUE self)
269
+ {
270
+ return rb_check_typeddata(self, &decompressor_data_type);
271
+ }
272
+
273
+ static long
274
+ next_power_of_two(long n) {
275
+ n--;
276
+ n |= n >> 1;
277
+ n |= n >> 2;
278
+ n |= n >> 4;
279
+ n |= n >> 8;
280
+ n |= n >> 16;
281
+ #if LONG_MAX > UINT32_MAX
282
+ n |= n >> 32;
283
+ #endif
284
+ n++;
285
+ return n;
286
+ }
287
+
288
+ /*
289
+ * call-seq:
290
+ * decompressor.decompress(str, format = nil, outbuf = nil) -> string
291
+ *
292
+ * Decompresses the given string compressed in <i>format</i>. Valid values of
293
+ * <i>format</i> are DEFLATE (default), ZLIB and GZIP. If <i>outbuf</i> is
294
+ * given, the resulting uncompressed data will be written to it.
295
+ *
296
+ * decompressor.decompress("\x01\x03\x00\xFC\xFFfoo") #=> "foo"
297
+ * decompressor.decompress("x\x9C\x01\x03\x00\xFC\xFFfoo\x02\x82\x01E", Libdeflate::ZLIB) #=> "foo"
298
+ *
299
+ * outbuf = 'bar'
300
+ * decompressor.decompress("\x01\x03\x00\xFC\xFFfoo", nil, outbuf) #=> "foo"
301
+ * outbuf #=> "foo"
302
+ */
303
+ static VALUE
304
+ rb_compressor_decompress(int argc, VALUE *argv, VALUE self)
305
+ {
306
+ struct libdeflate_decompressor *d = check_decompressor(self);
307
+ VALUE str, format, outbuf;
308
+ enum libdeflate_result (*decompress_func)(struct libdeflate_decompressor *, const void *, size_t, void *, size_t, size_t *);
309
+ size_t actual_out_nbytes_ret;
310
+ enum libdeflate_result decompress_result;
311
+
312
+ rb_scan_args(argc, argv, "12", &str, &format, &outbuf);
313
+
314
+ StringValue(str);
315
+
316
+ switch (NIL_P(format) ? FORMAT_DEFLATE : FIX2INT(format)) {
317
+ case FORMAT_DEFLATE:
318
+ decompress_func = &libdeflate_deflate_decompress;
319
+ break;
320
+ case FORMAT_ZLIB:
321
+ decompress_func = &libdeflate_zlib_decompress;
322
+ break;
323
+ case FORMAT_GZIP:
324
+ decompress_func = &libdeflate_gzip_decompress;
325
+ break;
326
+ default:
327
+ rb_raise(rb_eLibdefalteError, "unknown compressed data format: %d", FIX2INT(format));
328
+ }
329
+
330
+ if (NIL_P(outbuf)) {
331
+ outbuf = rb_str_buf_new(next_power_of_two(RSTRING_LEN(str)) << 4);
332
+ } else {
333
+ StringValue(outbuf);
334
+ rb_str_modify(outbuf);
335
+ }
336
+
337
+ for (;;) {
338
+ decompress_result = decompress_func(d,
339
+ RSTRING_PTR(str),
340
+ RSTRING_LEN(str),
341
+ RSTRING_PTR(outbuf),
342
+ rb_str_capacity(outbuf),
343
+ &actual_out_nbytes_ret);
344
+
345
+ if (decompress_result != LIBDEFLATE_INSUFFICIENT_SPACE) {
346
+ break;
347
+ }
348
+
349
+ rb_str_modify_expand(outbuf, (rb_str_capacity(outbuf) << 1) - RSTRING_LEN(outbuf));
350
+ }
351
+
352
+ if (decompress_result == LIBDEFLATE_BAD_DATA) {
353
+ rb_raise(rb_eBadDataError, "failed to decompress data");
354
+ } else if (decompress_result != LIBDEFLATE_SUCCESS) {
355
+ rb_raise(rb_eLibdefalteError, "failed to decompress data");
356
+ }
357
+
358
+ rb_str_set_len(outbuf, actual_out_nbytes_ret);
359
+ OBJ_INFECT(outbuf, str);
360
+
361
+ return outbuf;
362
+ }
363
+
364
+ void
365
+ Init_libdeflate_ext(void)
366
+ {
367
+ VALUE rb_mLibdeflate, rb_cCompressor, rb_cDecompressor;
368
+
369
+ rb_mLibdeflate = rb_define_module("Libdeflate");
370
+ rb_eLibdefalteError = rb_define_class_under(rb_mLibdeflate, "Error", rb_eStandardError);
371
+ rb_eBadDataError = rb_define_class_under(rb_mLibdeflate, "BadDataError", rb_eLibdefalteError);
372
+
373
+ rb_define_const(rb_mLibdeflate, "DEFLATE", INT2FIX(FORMAT_DEFLATE));
374
+ rb_define_const(rb_mLibdeflate, "ZLIB", INT2FIX(FORMAT_ZLIB));
375
+ rb_define_const(rb_mLibdeflate, "GZIP", INT2FIX(FORMAT_GZIP));
376
+
377
+ rb_define_module_function(rb_mLibdeflate, "adler32", rb_libdeflate_adler32, -1);
378
+ rb_define_module_function(rb_mLibdeflate, "crc32", rb_libdeflate_crc32, -1);
379
+
380
+ rb_cCompressor = rb_define_class_under(rb_mLibdeflate, "Compressor", rb_cObject);
381
+ rb_define_alloc_func(rb_cCompressor, rb_compressor_s_allocate);
382
+ rb_define_method(rb_cCompressor, "initialize", rb_compressor_initialize, -1);
383
+ rb_define_method(rb_cCompressor, "compress", rb_compressor_compress, -1);
384
+
385
+ rb_cDecompressor = rb_define_class_under(rb_mLibdeflate, "Decompressor", rb_cObject);
386
+ rb_define_alloc_func(rb_cDecompressor, rb_decompressor_s_allocate);
387
+ rb_define_method(rb_cDecompressor, "initialize", rb_decompressor_initialize, 0);
388
+ rb_define_method(rb_cDecompressor, "decompress", rb_compressor_decompress, -1);
389
+ }