libdeflate 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.gitmodules +3 -0
- data/.rspec +2 -0
- data/.rubocop.yml +1 -0
- data/.rubocop_todo.yml +9 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +52 -0
- data/Rakefile +15 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/ext/libdeflate/extconf.rb +14 -0
- data/ext/libdeflate/libdeflate/.gitignore +19 -0
- data/ext/libdeflate/libdeflate/COPYING +21 -0
- data/ext/libdeflate/libdeflate/Makefile +231 -0
- data/ext/libdeflate/libdeflate/Makefile.msc +64 -0
- data/ext/libdeflate/libdeflate/NEWS +57 -0
- data/ext/libdeflate/libdeflate/README.md +170 -0
- data/ext/libdeflate/libdeflate/common/common_defs.h +351 -0
- data/ext/libdeflate/libdeflate/common/compiler_gcc.h +134 -0
- data/ext/libdeflate/libdeflate/common/compiler_msc.h +95 -0
- data/ext/libdeflate/libdeflate/lib/adler32.c +213 -0
- data/ext/libdeflate/libdeflate/lib/adler32_impl.h +281 -0
- data/ext/libdeflate/libdeflate/lib/aligned_malloc.c +57 -0
- data/ext/libdeflate/libdeflate/lib/aligned_malloc.h +13 -0
- data/ext/libdeflate/libdeflate/lib/bt_matchfinder.h +357 -0
- data/ext/libdeflate/libdeflate/lib/crc32.c +368 -0
- data/ext/libdeflate/libdeflate/lib/crc32_impl.h +286 -0
- data/ext/libdeflate/libdeflate/lib/crc32_table.h +526 -0
- data/ext/libdeflate/libdeflate/lib/decompress_impl.h +404 -0
- data/ext/libdeflate/libdeflate/lib/deflate_compress.c +2817 -0
- data/ext/libdeflate/libdeflate/lib/deflate_compress.h +14 -0
- data/ext/libdeflate/libdeflate/lib/deflate_constants.h +66 -0
- data/ext/libdeflate/libdeflate/lib/deflate_decompress.c +889 -0
- data/ext/libdeflate/libdeflate/lib/gzip_compress.c +95 -0
- data/ext/libdeflate/libdeflate/lib/gzip_constants.h +45 -0
- data/ext/libdeflate/libdeflate/lib/gzip_decompress.c +130 -0
- data/ext/libdeflate/libdeflate/lib/hc_matchfinder.h +405 -0
- data/ext/libdeflate/libdeflate/lib/lib_common.h +35 -0
- data/ext/libdeflate/libdeflate/lib/matchfinder_avx2.h +53 -0
- data/ext/libdeflate/libdeflate/lib/matchfinder_common.h +205 -0
- data/ext/libdeflate/libdeflate/lib/matchfinder_neon.h +61 -0
- data/ext/libdeflate/libdeflate/lib/matchfinder_sse2.h +53 -0
- data/ext/libdeflate/libdeflate/lib/unaligned.h +202 -0
- data/ext/libdeflate/libdeflate/lib/x86_cpu_features.c +169 -0
- data/ext/libdeflate/libdeflate/lib/x86_cpu_features.h +48 -0
- data/ext/libdeflate/libdeflate/lib/zlib_compress.c +87 -0
- data/ext/libdeflate/libdeflate/lib/zlib_constants.h +21 -0
- data/ext/libdeflate/libdeflate/lib/zlib_decompress.c +91 -0
- data/ext/libdeflate/libdeflate/libdeflate.h +274 -0
- data/ext/libdeflate/libdeflate/programs/benchmark.c +558 -0
- data/ext/libdeflate/libdeflate/programs/checksum.c +197 -0
- data/ext/libdeflate/libdeflate/programs/detect.sh +62 -0
- data/ext/libdeflate/libdeflate/programs/gzip.c +603 -0
- data/ext/libdeflate/libdeflate/programs/prog_util.c +530 -0
- data/ext/libdeflate/libdeflate/programs/prog_util.h +162 -0
- data/ext/libdeflate/libdeflate/programs/test_checksums.c +135 -0
- data/ext/libdeflate/libdeflate/programs/tgetopt.c +118 -0
- data/ext/libdeflate/libdeflate/tools/afl-fuzz/Makefile +12 -0
- data/ext/libdeflate/libdeflate/tools/afl-fuzz/deflate_compress/fuzz.c +40 -0
- data/ext/libdeflate/libdeflate/tools/afl-fuzz/deflate_compress/inputs/0 +0 -0
- data/ext/libdeflate/libdeflate/tools/afl-fuzz/deflate_decompress/fuzz.c +28 -0
- data/ext/libdeflate/libdeflate/tools/afl-fuzz/deflate_decompress/inputs/0 +3 -0
- data/ext/libdeflate/libdeflate/tools/afl-fuzz/gzip_decompress/fuzz.c +28 -0
- data/ext/libdeflate/libdeflate/tools/afl-fuzz/gzip_decompress/inputs/0 +0 -0
- data/ext/libdeflate/libdeflate/tools/afl-fuzz/prepare_for_fuzz.sh +14 -0
- data/ext/libdeflate/libdeflate/tools/afl-fuzz/zlib_decompress/fuzz.c +28 -0
- data/ext/libdeflate/libdeflate/tools/afl-fuzz/zlib_decompress/inputs/0 +3 -0
- data/ext/libdeflate/libdeflate/tools/android_build.sh +104 -0
- data/ext/libdeflate/libdeflate/tools/checksum_benchmarks.sh +76 -0
- data/ext/libdeflate/libdeflate/tools/exec_tests.sh +30 -0
- data/ext/libdeflate/libdeflate/tools/gen_crc32_multipliers.c +108 -0
- data/ext/libdeflate/libdeflate/tools/gen_crc32_table.c +100 -0
- data/ext/libdeflate/libdeflate/tools/gzip_tests.sh +412 -0
- data/ext/libdeflate/libdeflate/tools/make-windows-releases +21 -0
- data/ext/libdeflate/libdeflate/tools/mips_build.sh +9 -0
- data/ext/libdeflate/libdeflate/tools/msc_test.bat +3 -0
- data/ext/libdeflate/libdeflate/tools/pgo_build.sh +23 -0
- data/ext/libdeflate/libdeflate/tools/produce_gzip_benchmark_table.sh +37 -0
- data/ext/libdeflate/libdeflate/tools/run_tests.sh +305 -0
- data/ext/libdeflate/libdeflate/tools/windows_build.sh +10 -0
- data/ext/libdeflate/libdeflate_ext.c +389 -0
- data/ext/libdeflate/libdeflate_ext.h +8 -0
- data/lib/libdeflate.rb +2 -0
- data/lib/libdeflate/version.rb +3 -0
- data/libdeflate.gemspec +33 -0
- 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,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
|
+
}
|