ruby-zstds 1.0.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/AUTHORS +1 -0
- data/LICENSE +21 -0
- data/README.md +498 -0
- data/ext/extconf.rb +82 -0
- data/ext/zstds_ext/buffer.c +30 -0
- data/ext/zstds_ext/buffer.h +23 -0
- data/ext/zstds_ext/common.h +16 -0
- data/ext/zstds_ext/dictionary.c +106 -0
- data/ext/zstds_ext/dictionary.h +16 -0
- data/ext/zstds_ext/error.c +81 -0
- data/ext/zstds_ext/error.h +35 -0
- data/ext/zstds_ext/io.c +512 -0
- data/ext/zstds_ext/io.h +14 -0
- data/ext/zstds_ext/macro.h +13 -0
- data/ext/zstds_ext/main.c +25 -0
- data/ext/zstds_ext/option.c +287 -0
- data/ext/zstds_ext/option.h +122 -0
- data/ext/zstds_ext/stream/compressor.c +241 -0
- data/ext/zstds_ext/stream/compressor.h +31 -0
- data/ext/zstds_ext/stream/decompressor.c +183 -0
- data/ext/zstds_ext/stream/decompressor.h +29 -0
- data/ext/zstds_ext/string.c +254 -0
- data/ext/zstds_ext/string.h +14 -0
- data/lib/zstds.rb +9 -0
- data/lib/zstds/dictionary.rb +47 -0
- data/lib/zstds/error.rb +22 -0
- data/lib/zstds/file.rb +46 -0
- data/lib/zstds/option.rb +194 -0
- data/lib/zstds/stream/abstract.rb +153 -0
- data/lib/zstds/stream/delegates.rb +36 -0
- data/lib/zstds/stream/raw/abstract.rb +55 -0
- data/lib/zstds/stream/raw/compressor.rb +101 -0
- data/lib/zstds/stream/raw/decompressor.rb +70 -0
- data/lib/zstds/stream/reader.rb +166 -0
- data/lib/zstds/stream/reader_helpers.rb +192 -0
- data/lib/zstds/stream/stat.rb +78 -0
- data/lib/zstds/stream/writer.rb +145 -0
- data/lib/zstds/stream/writer_helpers.rb +93 -0
- data/lib/zstds/string.rb +31 -0
- data/lib/zstds/validation.rb +48 -0
- data/lib/zstds/version.rb +6 -0
- metadata +182 -0
data/ext/extconf.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
# Ruby bindings for zstd library.
|
2
|
+
# Copyright (c) 2019 AUTHORS, MIT License.
|
3
|
+
|
4
|
+
require "mkmf"
|
5
|
+
|
6
|
+
$LDFLAGS << " -pthread" # rubocop:disable Style/GlobalVars
|
7
|
+
|
8
|
+
def require_header(name, types = [])
|
9
|
+
abort "Can't find #{name} header" unless find_header name
|
10
|
+
|
11
|
+
types.each do |type|
|
12
|
+
abort "Can't find #{type} type in #{name} header" unless find_type type, nil, name
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
require_header "zstd_errors.h", %w[
|
17
|
+
ZSTD_ErrorCode
|
18
|
+
]
|
19
|
+
require_header "zstd.h", [
|
20
|
+
"ZSTD_CCtx *",
|
21
|
+
"ZSTD_DCtx *",
|
22
|
+
"ZSTD_strategy",
|
23
|
+
"ZSTD_bounds",
|
24
|
+
"ZSTD_inBuffer",
|
25
|
+
"ZSTD_outBuffer"
|
26
|
+
]
|
27
|
+
require_header "zdict.h"
|
28
|
+
|
29
|
+
def require_library(name, functions)
|
30
|
+
functions.each do |function|
|
31
|
+
abort "Can't find #{function} function in #{name} library" unless find_library name, function
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
require_library(
|
36
|
+
"zstd",
|
37
|
+
%w[
|
38
|
+
ZSTD_isError
|
39
|
+
ZSTD_getErrorCode
|
40
|
+
ZSTD_createCCtx
|
41
|
+
ZSTD_createDCtx
|
42
|
+
ZSTD_freeCCtx
|
43
|
+
ZSTD_freeDCtx
|
44
|
+
ZSTD_CCtx_setParameter
|
45
|
+
ZSTD_DCtx_setParameter
|
46
|
+
ZSTD_CCtx_setPledgedSrcSize
|
47
|
+
ZSTD_cParam_getBounds
|
48
|
+
ZSTD_dParam_getBounds
|
49
|
+
ZSTD_CStreamInSize
|
50
|
+
ZSTD_CStreamOutSize
|
51
|
+
ZSTD_DStreamInSize
|
52
|
+
ZSTD_DStreamOutSize
|
53
|
+
ZSTD_compressStream2
|
54
|
+
ZSTD_decompressStream
|
55
|
+
ZDICT_getDictID
|
56
|
+
ZDICT_trainFromBuffer
|
57
|
+
]
|
58
|
+
)
|
59
|
+
|
60
|
+
extension_name = "zstds_ext".freeze
|
61
|
+
dir_config extension_name
|
62
|
+
|
63
|
+
# rubocop:disable Style/GlobalVars
|
64
|
+
$srcs = %w[
|
65
|
+
stream/compressor
|
66
|
+
stream/decompressor
|
67
|
+
buffer
|
68
|
+
dictionary
|
69
|
+
error
|
70
|
+
io
|
71
|
+
main
|
72
|
+
option
|
73
|
+
string
|
74
|
+
]
|
75
|
+
.map { |name| "src/#{extension_name}/#{name}.c" }
|
76
|
+
.freeze
|
77
|
+
|
78
|
+
$CFLAGS << " -Wno-declaration-after-statement"
|
79
|
+
$VPATH << "$(srcdir)/#{extension_name}:$(srcdir)/#{extension_name}/stream"
|
80
|
+
# rubocop:enable Style/GlobalVars
|
81
|
+
|
82
|
+
create_makefile extension_name
|
@@ -0,0 +1,30 @@
|
|
1
|
+
// Ruby bindings for zstd library.
|
2
|
+
// Copyright (c) 2019 AUTHORS, MIT License.
|
3
|
+
|
4
|
+
#include "zstds_ext/buffer.h"
|
5
|
+
|
6
|
+
#include <zstd.h>
|
7
|
+
|
8
|
+
#include "ruby.h"
|
9
|
+
|
10
|
+
VALUE zstds_ext_create_string_buffer(VALUE length)
|
11
|
+
{
|
12
|
+
return rb_str_new(NULL, NUM2SIZET(length));
|
13
|
+
}
|
14
|
+
|
15
|
+
VALUE zstds_ext_resize_string_buffer(VALUE args)
|
16
|
+
{
|
17
|
+
VALUE buffer = rb_ary_entry(args, 0);
|
18
|
+
VALUE length = rb_ary_entry(args, 1);
|
19
|
+
return rb_str_resize(buffer, NUM2SIZET(length));
|
20
|
+
}
|
21
|
+
|
22
|
+
void zstds_ext_buffer_exports(VALUE root_module)
|
23
|
+
{
|
24
|
+
VALUE module = rb_define_module_under(root_module, "Buffer");
|
25
|
+
|
26
|
+
rb_define_const(module, "DEFAULT_SOURCE_BUFFER_LENGTH_FOR_COMPRESSOR", SIZET2NUM(ZSTD_CStreamInSize()));
|
27
|
+
rb_define_const(module, "DEFAULT_DESTINATION_BUFFER_LENGTH_FOR_COMPRESSOR", SIZET2NUM(ZSTD_CStreamOutSize()));
|
28
|
+
rb_define_const(module, "DEFAULT_SOURCE_BUFFER_LENGTH_FOR_DECOMPRESSOR", SIZET2NUM(ZSTD_DStreamInSize()));
|
29
|
+
rb_define_const(module, "DEFAULT_DESTINATION_BUFFER_LENGTH_FOR_DECOMPRESSOR", SIZET2NUM(ZSTD_DStreamOutSize()));
|
30
|
+
}
|
@@ -0,0 +1,23 @@
|
|
1
|
+
// Ruby bindings for zstd library.
|
2
|
+
// Copyright (c) 2019 AUTHORS, MIT License.
|
3
|
+
|
4
|
+
#if !defined(ZSTDS_EXT_BUFFER_H)
|
5
|
+
#define ZSTDS_EXT_BUFFER_H
|
6
|
+
|
7
|
+
#include "ruby.h"
|
8
|
+
|
9
|
+
VALUE zstds_ext_create_string_buffer(VALUE length);
|
10
|
+
|
11
|
+
#define ZSTDS_EXT_CREATE_STRING_BUFFER(buffer, length, exception) \
|
12
|
+
VALUE buffer = rb_protect(zstds_ext_create_string_buffer, SIZET2NUM(length), &exception);
|
13
|
+
|
14
|
+
VALUE zstds_ext_resize_string_buffer(VALUE args);
|
15
|
+
|
16
|
+
#define ZSTDS_EXT_RESIZE_STRING_BUFFER(buffer, length, exception) \
|
17
|
+
VALUE args = rb_ary_new_from_args(2, buffer, SIZET2NUM(length)); \
|
18
|
+
buffer = rb_protect(zstds_ext_resize_string_buffer, args, &exception); \
|
19
|
+
RB_GC_GUARD(args);
|
20
|
+
|
21
|
+
void zstds_ext_buffer_exports(VALUE root_module);
|
22
|
+
|
23
|
+
#endif // ZSTDS_EXT_BUFFER_H
|
@@ -0,0 +1,16 @@
|
|
1
|
+
// Ruby bindings for zstd library.
|
2
|
+
// Copyright (c) 2019 AUTHORS, MIT License.
|
3
|
+
|
4
|
+
#if !defined(ZSTDS_EXT_COMMON_H)
|
5
|
+
#define ZSTDS_EXT_COMMON_H
|
6
|
+
|
7
|
+
#include <stdint.h>
|
8
|
+
#include <stdlib.h>
|
9
|
+
|
10
|
+
#define ZSTDS_EXT_MODULE_NAME "ZSTDS"
|
11
|
+
|
12
|
+
// WARNING: zstd library are mixing size and error codes inside size_t, dangerous.
|
13
|
+
typedef size_t zstds_result_t;
|
14
|
+
typedef uint_fast8_t zstds_ext_result_t;
|
15
|
+
|
16
|
+
#endif // ZSTDS_EXT_COMMON_H
|
@@ -0,0 +1,106 @@
|
|
1
|
+
// Ruby bindings for zstd library.
|
2
|
+
// Copyright (c) 2019 AUTHORS, MIT License.
|
3
|
+
|
4
|
+
#include "zstds_ext/dictionary.h"
|
5
|
+
|
6
|
+
#include <stdint.h>
|
7
|
+
#include <stdlib.h>
|
8
|
+
#include <string.h>
|
9
|
+
#include <zdict.h>
|
10
|
+
|
11
|
+
#include "ruby.h"
|
12
|
+
#include "zstds_ext/buffer.h"
|
13
|
+
#include "zstds_ext/common.h"
|
14
|
+
#include "zstds_ext/error.h"
|
15
|
+
#include "zstds_ext/macro.h"
|
16
|
+
#include "zstds_ext/option.h"
|
17
|
+
|
18
|
+
VALUE zstds_ext_get_dictionary_buffer_id(VALUE ZSTDS_EXT_UNUSED(self), VALUE buffer)
|
19
|
+
{
|
20
|
+
unsigned int id = ZDICT_getDictID(RSTRING_PTR(buffer), RSTRING_LEN(buffer));
|
21
|
+
if (id == 0) {
|
22
|
+
zstds_ext_raise_error(ZSTDS_EXT_ERROR_VALIDATE_FAILED);
|
23
|
+
}
|
24
|
+
|
25
|
+
return UINT2NUM(id);
|
26
|
+
}
|
27
|
+
|
28
|
+
VALUE zstds_ext_train_dictionary_buffer(VALUE ZSTDS_EXT_UNUSED(self), VALUE samples, VALUE options)
|
29
|
+
{
|
30
|
+
Check_Type(samples, T_ARRAY);
|
31
|
+
|
32
|
+
size_t sample_index;
|
33
|
+
unsigned int samples_length = (unsigned int)RARRAY_LEN(samples);
|
34
|
+
size_t samples_size = 0;
|
35
|
+
|
36
|
+
for (sample_index = 0; sample_index < samples_length; sample_index++) {
|
37
|
+
VALUE sample = rb_ary_entry(samples, sample_index);
|
38
|
+
Check_Type(sample, T_STRING);
|
39
|
+
|
40
|
+
samples_size += RSTRING_LEN(sample);
|
41
|
+
}
|
42
|
+
|
43
|
+
Check_Type(options, T_HASH);
|
44
|
+
ZSTDS_EXT_GET_BUFFER_LENGTH_OPTION(options, capacity);
|
45
|
+
|
46
|
+
if (capacity == 0) {
|
47
|
+
capacity = ZSTDS_EXT_DEFAULT_DICTIONARY_CAPACITY;
|
48
|
+
}
|
49
|
+
|
50
|
+
int exception;
|
51
|
+
|
52
|
+
ZSTDS_EXT_CREATE_STRING_BUFFER(buffer, capacity, exception);
|
53
|
+
if (exception != 0) {
|
54
|
+
zstds_ext_raise_error(ZSTDS_EXT_ERROR_ALLOCATE_FAILED);
|
55
|
+
}
|
56
|
+
|
57
|
+
uint8_t* samples_buffer = malloc(samples_size);
|
58
|
+
if (samples_buffer == NULL) {
|
59
|
+
zstds_ext_raise_error(ZSTDS_EXT_ERROR_ALLOCATE_FAILED);
|
60
|
+
}
|
61
|
+
|
62
|
+
size_t* samples_sizes = malloc(samples_length * sizeof(size_t));
|
63
|
+
if (samples_sizes == NULL) {
|
64
|
+
free(samples_buffer);
|
65
|
+
zstds_ext_raise_error(ZSTDS_EXT_ERROR_ALLOCATE_FAILED);
|
66
|
+
}
|
67
|
+
|
68
|
+
size_t sample_offset = 0;
|
69
|
+
|
70
|
+
for (sample_index = 0; sample_index < samples_length; sample_index++) {
|
71
|
+
VALUE sample = rb_ary_entry(samples, sample_index);
|
72
|
+
const char* sample_data = RSTRING_PTR(sample);
|
73
|
+
size_t sample_size = RSTRING_LEN(sample);
|
74
|
+
|
75
|
+
memmove(samples_buffer + sample_offset, sample_data, sample_size);
|
76
|
+
sample_offset += sample_size;
|
77
|
+
|
78
|
+
samples_sizes[sample_index] = sample_size;
|
79
|
+
}
|
80
|
+
|
81
|
+
zstds_result_t result = ZDICT_trainFromBuffer(
|
82
|
+
RSTRING_PTR(buffer), capacity,
|
83
|
+
samples_buffer, samples_sizes, samples_length);
|
84
|
+
|
85
|
+
free(samples_buffer);
|
86
|
+
free(samples_sizes);
|
87
|
+
|
88
|
+
if (ZSTD_isError(result)) {
|
89
|
+
zstds_ext_raise_error(zstds_ext_get_error(ZSTD_getErrorCode(result)));
|
90
|
+
}
|
91
|
+
|
92
|
+
ZSTDS_EXT_RESIZE_STRING_BUFFER(buffer, result, exception);
|
93
|
+
if (exception != 0) {
|
94
|
+
zstds_ext_raise_error(ZSTDS_EXT_ERROR_ALLOCATE_FAILED);
|
95
|
+
}
|
96
|
+
|
97
|
+
return buffer;
|
98
|
+
}
|
99
|
+
|
100
|
+
void zstds_ext_dictionary_exports(VALUE root_module)
|
101
|
+
{
|
102
|
+
VALUE dictionary = rb_define_class_under(root_module, "Dictionary", rb_cObject);
|
103
|
+
|
104
|
+
rb_define_singleton_method(dictionary, "get_buffer_id", zstds_ext_get_dictionary_buffer_id, 1);
|
105
|
+
rb_define_singleton_method(dictionary, "train_buffer", zstds_ext_train_dictionary_buffer, 2);
|
106
|
+
}
|
@@ -0,0 +1,16 @@
|
|
1
|
+
// Ruby bindings for zstd library.
|
2
|
+
// Copyright (c) 2019 AUTHORS, MIT License.
|
3
|
+
|
4
|
+
#if !defined(ZSTDS_EXT_DICTIONARY_H)
|
5
|
+
#define ZSTDS_EXT_DICTIONARY_H
|
6
|
+
|
7
|
+
#include "ruby.h"
|
8
|
+
|
9
|
+
#define ZSTDS_EXT_DEFAULT_DICTIONARY_CAPACITY (1 << 17); // 128 KB
|
10
|
+
|
11
|
+
VALUE zstds_ext_get_dictionary_buffer_id(VALUE self, VALUE buffer);
|
12
|
+
VALUE zstds_ext_train_dictionary_buffer(VALUE self, VALUE samples, VALUE options);
|
13
|
+
|
14
|
+
void zstds_ext_dictionary_exports(VALUE root_module);
|
15
|
+
|
16
|
+
#endif // ZSTDS_EXT_DICTIONARY_H
|
@@ -0,0 +1,81 @@
|
|
1
|
+
// Ruby bindings for zstd library.
|
2
|
+
// Copyright (c) 2019 AUTHORS, MIT License.
|
3
|
+
|
4
|
+
#include "zstds_ext/error.h"
|
5
|
+
|
6
|
+
#include <zstd_errors.h>
|
7
|
+
|
8
|
+
#include "ruby.h"
|
9
|
+
#include "zstds_ext/common.h"
|
10
|
+
|
11
|
+
zstds_ext_result_t zstds_ext_get_error(ZSTD_ErrorCode error_code)
|
12
|
+
{
|
13
|
+
switch (error_code) {
|
14
|
+
case ZSTD_error_memory_allocation:
|
15
|
+
return ZSTDS_EXT_ERROR_ALLOCATE_FAILED;
|
16
|
+
case ZSTD_error_parameter_unsupported:
|
17
|
+
case ZSTD_error_parameter_outOfBound:
|
18
|
+
case ZSTD_error_tableLog_tooLarge:
|
19
|
+
case ZSTD_error_maxSymbolValue_tooLarge:
|
20
|
+
case ZSTD_error_maxSymbolValue_tooSmall:
|
21
|
+
case ZSTD_error_stage_wrong:
|
22
|
+
case ZSTD_error_init_missing:
|
23
|
+
case ZSTD_error_workSpace_tooSmall:
|
24
|
+
case ZSTD_error_srcSize_wrong:
|
25
|
+
case ZSTD_error_dstSize_tooSmall:
|
26
|
+
case ZSTD_error_dstBuffer_null:
|
27
|
+
return ZSTDS_EXT_ERROR_VALIDATE_FAILED;
|
28
|
+
case ZSTD_error_prefix_unknown:
|
29
|
+
case ZSTD_error_version_unsupported:
|
30
|
+
case ZSTD_error_frameParameter_unsupported:
|
31
|
+
case ZSTD_error_frameParameter_windowTooLarge:
|
32
|
+
case ZSTD_error_corruption_detected:
|
33
|
+
case ZSTD_error_checksum_wrong:
|
34
|
+
return ZSTDS_EXT_ERROR_DECOMPRESSOR_CORRUPTED_SOURCE;
|
35
|
+
case ZSTD_error_dictionary_corrupted:
|
36
|
+
case ZSTD_error_dictionary_wrong:
|
37
|
+
case ZSTD_error_dictionaryCreation_failed:
|
38
|
+
return ZSTDS_EXT_ERROR_CORRUPTED_DICTIONARY;
|
39
|
+
default:
|
40
|
+
return ZSTDS_EXT_ERROR_UNEXPECTED;
|
41
|
+
}
|
42
|
+
}
|
43
|
+
|
44
|
+
static inline NORETURN(void raise(const char *name, const char *description))
|
45
|
+
{
|
46
|
+
VALUE module = rb_define_module(ZSTDS_EXT_MODULE_NAME);
|
47
|
+
VALUE error = rb_const_get(module, rb_intern(name));
|
48
|
+
rb_raise(error, "%s", description);
|
49
|
+
}
|
50
|
+
|
51
|
+
void zstds_ext_raise_error(zstds_ext_result_t ext_result)
|
52
|
+
{
|
53
|
+
switch (ext_result) {
|
54
|
+
case ZSTDS_EXT_ERROR_ALLOCATE_FAILED:
|
55
|
+
raise("AllocateError", "allocate error");
|
56
|
+
case ZSTDS_EXT_ERROR_VALIDATE_FAILED:
|
57
|
+
raise("ValidateError", "validate error");
|
58
|
+
|
59
|
+
case ZSTDS_EXT_ERROR_USED_AFTER_CLOSE:
|
60
|
+
raise("UsedAfterCloseError", "used after closed");
|
61
|
+
case ZSTDS_EXT_ERROR_NOT_ENOUGH_SOURCE_BUFFER:
|
62
|
+
raise("NotEnoughSourceBufferError", "not enough source buffer");
|
63
|
+
case ZSTDS_EXT_ERROR_NOT_ENOUGH_DESTINATION_BUFFER:
|
64
|
+
raise("NotEnoughDestinationBufferError", "not enough destination buffer");
|
65
|
+
case ZSTDS_EXT_ERROR_DECOMPRESSOR_CORRUPTED_SOURCE:
|
66
|
+
raise("DecompressorCorruptedSourceError", "decompressor received corrupted source");
|
67
|
+
case ZSTDS_EXT_ERROR_CORRUPTED_DICTIONARY:
|
68
|
+
raise("CorruptedDictionaryError", "corrupted dictionary");
|
69
|
+
|
70
|
+
case ZSTDS_EXT_ERROR_ACCESS_IO:
|
71
|
+
raise("AccessIOError", "failed to access IO");
|
72
|
+
case ZSTDS_EXT_ERROR_READ_IO:
|
73
|
+
raise("ReadIOError", "failed to read IO");
|
74
|
+
case ZSTDS_EXT_ERROR_WRITE_IO:
|
75
|
+
raise("WriteIOError", "failed to write IO");
|
76
|
+
|
77
|
+
default:
|
78
|
+
// ZSTDS_EXT_ERROR_UNEXPECTED
|
79
|
+
raise("UnexpectedError", "unexpected error");
|
80
|
+
}
|
81
|
+
}
|
@@ -0,0 +1,35 @@
|
|
1
|
+
// Ruby bindings for zstd library.
|
2
|
+
// Copyright (c) 2019 AUTHORS, MIT License.
|
3
|
+
|
4
|
+
#if !defined(ZSTDS_EXT_ERROR_H)
|
5
|
+
#define ZSTDS_EXT_ERROR_H
|
6
|
+
|
7
|
+
#include <zstd_errors.h>
|
8
|
+
|
9
|
+
#include "ruby.h"
|
10
|
+
#include "zstds_ext/common.h"
|
11
|
+
|
12
|
+
// Results for errors listed in "lib/zstds/error" used in c extension.
|
13
|
+
|
14
|
+
enum {
|
15
|
+
ZSTDS_EXT_ERROR_ALLOCATE_FAILED = 1,
|
16
|
+
ZSTDS_EXT_ERROR_VALIDATE_FAILED,
|
17
|
+
|
18
|
+
ZSTDS_EXT_ERROR_USED_AFTER_CLOSE,
|
19
|
+
ZSTDS_EXT_ERROR_NOT_ENOUGH_SOURCE_BUFFER,
|
20
|
+
ZSTDS_EXT_ERROR_NOT_ENOUGH_DESTINATION_BUFFER,
|
21
|
+
ZSTDS_EXT_ERROR_DECOMPRESSOR_CORRUPTED_SOURCE,
|
22
|
+
ZSTDS_EXT_ERROR_CORRUPTED_DICTIONARY,
|
23
|
+
|
24
|
+
ZSTDS_EXT_ERROR_ACCESS_IO,
|
25
|
+
ZSTDS_EXT_ERROR_READ_IO,
|
26
|
+
ZSTDS_EXT_ERROR_WRITE_IO,
|
27
|
+
|
28
|
+
ZSTDS_EXT_ERROR_UNEXPECTED
|
29
|
+
};
|
30
|
+
|
31
|
+
zstds_ext_result_t zstds_ext_get_error(ZSTD_ErrorCode error_code);
|
32
|
+
|
33
|
+
NORETURN(void zstds_ext_raise_error(zstds_ext_result_t ext_result));
|
34
|
+
|
35
|
+
#endif // ZSTDS_EXT_ERROR_H
|
data/ext/zstds_ext/io.c
ADDED
@@ -0,0 +1,512 @@
|
|
1
|
+
// Ruby bindings for zstd library.
|
2
|
+
// Copyright (c) 2019 AUTHORS, MIT License.
|
3
|
+
|
4
|
+
#include "ruby/io.h"
|
5
|
+
|
6
|
+
#include <stdint.h>
|
7
|
+
#include <stdio.h>
|
8
|
+
#include <stdlib.h>
|
9
|
+
#include <string.h>
|
10
|
+
#include <zstd.h>
|
11
|
+
|
12
|
+
#include "ruby.h"
|
13
|
+
#include "zstds_ext/common.h"
|
14
|
+
#include "zstds_ext/error.h"
|
15
|
+
#include "zstds_ext/io.h"
|
16
|
+
#include "zstds_ext/macro.h"
|
17
|
+
#include "zstds_ext/option.h"
|
18
|
+
|
19
|
+
// Additional possible results:
|
20
|
+
enum {
|
21
|
+
ZSTDS_EXT_FILE_READ_FINISHED = 128
|
22
|
+
};
|
23
|
+
|
24
|
+
// -- file --
|
25
|
+
|
26
|
+
static inline zstds_ext_result_t read_file(FILE* source_file, uint8_t* source_buffer, size_t* source_length_ptr, size_t source_buffer_length)
|
27
|
+
{
|
28
|
+
size_t read_length = fread(source_buffer, 1, source_buffer_length, source_file);
|
29
|
+
if (read_length == 0 && feof(source_file)) {
|
30
|
+
return ZSTDS_EXT_FILE_READ_FINISHED;
|
31
|
+
}
|
32
|
+
|
33
|
+
if (read_length != source_buffer_length && ferror(source_file)) {
|
34
|
+
return ZSTDS_EXT_ERROR_READ_IO;
|
35
|
+
}
|
36
|
+
|
37
|
+
*source_length_ptr = read_length;
|
38
|
+
|
39
|
+
return 0;
|
40
|
+
}
|
41
|
+
|
42
|
+
static inline zstds_ext_result_t write_file(FILE* destination_file, uint8_t* destination_buffer, size_t destination_length)
|
43
|
+
{
|
44
|
+
size_t written_length = fwrite(destination_buffer, 1, destination_length, destination_file);
|
45
|
+
if (written_length != destination_length) {
|
46
|
+
return ZSTDS_EXT_ERROR_WRITE_IO;
|
47
|
+
}
|
48
|
+
|
49
|
+
return 0;
|
50
|
+
}
|
51
|
+
|
52
|
+
// -- buffer --
|
53
|
+
|
54
|
+
static inline zstds_ext_result_t create_buffers(
|
55
|
+
uint8_t** source_buffer_ptr, size_t source_buffer_length,
|
56
|
+
uint8_t** destination_buffer_ptr, size_t destination_buffer_length)
|
57
|
+
{
|
58
|
+
uint8_t* source_buffer = malloc(source_buffer_length);
|
59
|
+
if (source_buffer == NULL) {
|
60
|
+
return ZSTDS_EXT_ERROR_ALLOCATE_FAILED;
|
61
|
+
}
|
62
|
+
|
63
|
+
uint8_t* destination_buffer = malloc(destination_buffer_length);
|
64
|
+
if (destination_buffer == NULL) {
|
65
|
+
free(source_buffer);
|
66
|
+
return ZSTDS_EXT_ERROR_ALLOCATE_FAILED;
|
67
|
+
}
|
68
|
+
|
69
|
+
*source_buffer_ptr = source_buffer;
|
70
|
+
*destination_buffer_ptr = destination_buffer;
|
71
|
+
|
72
|
+
return 0;
|
73
|
+
}
|
74
|
+
|
75
|
+
// We have read some source from file into source buffer.
|
76
|
+
// Than algorithm has read part of this source.
|
77
|
+
// We need to move remaining source to the top of source buffer.
|
78
|
+
// Than we can read more source from file.
|
79
|
+
// Algorithm can use same buffer again.
|
80
|
+
|
81
|
+
static inline zstds_ext_result_t read_more_source(
|
82
|
+
FILE* source_file,
|
83
|
+
const uint8_t** source_ptr, size_t* source_length_ptr,
|
84
|
+
uint8_t* source_buffer, size_t source_buffer_length)
|
85
|
+
{
|
86
|
+
const uint8_t* source = *source_ptr;
|
87
|
+
size_t source_length = *source_length_ptr;
|
88
|
+
|
89
|
+
if (source != source_buffer) {
|
90
|
+
if (source_length != 0) {
|
91
|
+
memmove(source_buffer, source, source_length);
|
92
|
+
}
|
93
|
+
|
94
|
+
// Source can be accessed even if next code will fail.
|
95
|
+
*source_ptr = source_buffer;
|
96
|
+
}
|
97
|
+
|
98
|
+
size_t remaining_source_buffer_length = source_buffer_length - source_length;
|
99
|
+
if (remaining_source_buffer_length == 0) {
|
100
|
+
// We want to read more data at once, than buffer has.
|
101
|
+
return ZSTDS_EXT_ERROR_NOT_ENOUGH_SOURCE_BUFFER;
|
102
|
+
}
|
103
|
+
|
104
|
+
uint8_t* remaining_source_buffer = source_buffer + source_length;
|
105
|
+
size_t new_source_length;
|
106
|
+
|
107
|
+
zstds_ext_result_t ext_result = read_file(source_file, remaining_source_buffer, &new_source_length, remaining_source_buffer_length);
|
108
|
+
if (ext_result != 0) {
|
109
|
+
return ext_result;
|
110
|
+
}
|
111
|
+
|
112
|
+
*source_length_ptr = source_length + new_source_length;
|
113
|
+
|
114
|
+
return 0;
|
115
|
+
}
|
116
|
+
|
117
|
+
#define BUFFERED_READ_SOURCE(function, ...) \
|
118
|
+
do { \
|
119
|
+
bool is_function_called = false; \
|
120
|
+
\
|
121
|
+
while (true) { \
|
122
|
+
ext_result = read_more_source( \
|
123
|
+
source_file, \
|
124
|
+
&source, &source_length, \
|
125
|
+
source_buffer, source_buffer_length); \
|
126
|
+
\
|
127
|
+
if (ext_result == ZSTDS_EXT_FILE_READ_FINISHED) { \
|
128
|
+
if (source_length != 0) { \
|
129
|
+
/* ZSTD won't provide any remainder by design. */ \
|
130
|
+
return ZSTDS_EXT_ERROR_READ_IO; \
|
131
|
+
} \
|
132
|
+
break; \
|
133
|
+
} \
|
134
|
+
else if (ext_result != 0) { \
|
135
|
+
return ext_result; \
|
136
|
+
} \
|
137
|
+
\
|
138
|
+
ext_result = function(__VA_ARGS__); \
|
139
|
+
if (ext_result != 0) { \
|
140
|
+
return ext_result; \
|
141
|
+
} \
|
142
|
+
\
|
143
|
+
is_function_called = true; \
|
144
|
+
} \
|
145
|
+
\
|
146
|
+
if (!is_function_called) { \
|
147
|
+
/* Function should be called at least once. */ \
|
148
|
+
ext_result = function(__VA_ARGS__); \
|
149
|
+
if (ext_result != 0) { \
|
150
|
+
return ext_result; \
|
151
|
+
} \
|
152
|
+
} \
|
153
|
+
} while (false);
|
154
|
+
|
155
|
+
// Algorithm has written data into destination buffer.
|
156
|
+
// We need to write this data into file.
|
157
|
+
// Than algorithm can use same buffer again.
|
158
|
+
|
159
|
+
static inline zstds_ext_result_t flush_destination_buffer(
|
160
|
+
FILE* destination_file,
|
161
|
+
uint8_t* destination_buffer, size_t* destination_length_ptr, size_t destination_buffer_length)
|
162
|
+
{
|
163
|
+
if (*destination_length_ptr == 0) {
|
164
|
+
// We want to write more data at once, than buffer has.
|
165
|
+
return ZSTDS_EXT_ERROR_NOT_ENOUGH_DESTINATION_BUFFER;
|
166
|
+
}
|
167
|
+
|
168
|
+
zstds_ext_result_t ext_result = write_file(destination_file, destination_buffer, *destination_length_ptr);
|
169
|
+
if (ext_result != 0) {
|
170
|
+
return ext_result;
|
171
|
+
}
|
172
|
+
|
173
|
+
*destination_length_ptr = 0;
|
174
|
+
|
175
|
+
return 0;
|
176
|
+
}
|
177
|
+
|
178
|
+
static inline zstds_ext_result_t write_remaining_destination(FILE* destination_file, uint8_t* destination_buffer, size_t destination_length)
|
179
|
+
{
|
180
|
+
if (destination_length == 0) {
|
181
|
+
return 0;
|
182
|
+
}
|
183
|
+
|
184
|
+
return write_file(destination_file, destination_buffer, destination_length);
|
185
|
+
}
|
186
|
+
|
187
|
+
// -- utils --
|
188
|
+
|
189
|
+
#define GET_FILE(target) \
|
190
|
+
Check_Type(target, T_FILE); \
|
191
|
+
\
|
192
|
+
rb_io_t* target##_io; \
|
193
|
+
GetOpenFile(target, target##_io); \
|
194
|
+
\
|
195
|
+
FILE* target##_file = rb_io_stdio_file(target##_io); \
|
196
|
+
if (target##_file == NULL) { \
|
197
|
+
zstds_ext_raise_error(ZSTDS_EXT_ERROR_ACCESS_IO); \
|
198
|
+
}
|
199
|
+
|
200
|
+
// -- compress --
|
201
|
+
|
202
|
+
static inline zstds_ext_result_t buffered_compress(
|
203
|
+
ZSTD_CCtx* ctx,
|
204
|
+
const uint8_t** source_ptr, size_t* source_length_ptr,
|
205
|
+
FILE* destination_file, uint8_t* destination_buffer, size_t* destination_length_ptr, size_t destination_buffer_length)
|
206
|
+
{
|
207
|
+
zstds_result_t result;
|
208
|
+
zstds_ext_result_t ext_result;
|
209
|
+
|
210
|
+
ZSTD_inBuffer in_buffer;
|
211
|
+
in_buffer.src = *source_ptr;
|
212
|
+
in_buffer.size = *source_length_ptr;
|
213
|
+
in_buffer.pos = 0;
|
214
|
+
|
215
|
+
ZSTD_outBuffer out_buffer;
|
216
|
+
|
217
|
+
while (true) {
|
218
|
+
out_buffer.dst = destination_buffer + *destination_length_ptr;
|
219
|
+
out_buffer.size = destination_buffer_length - *destination_length_ptr;
|
220
|
+
out_buffer.pos = 0;
|
221
|
+
|
222
|
+
result = ZSTD_compressStream2(ctx, &out_buffer, &in_buffer, ZSTD_e_continue);
|
223
|
+
if (ZSTD_isError(result)) {
|
224
|
+
return zstds_ext_get_error(ZSTD_getErrorCode(result));
|
225
|
+
}
|
226
|
+
|
227
|
+
*destination_length_ptr += out_buffer.pos;
|
228
|
+
|
229
|
+
if (*destination_length_ptr == destination_buffer_length) {
|
230
|
+
ext_result = flush_destination_buffer(
|
231
|
+
destination_file,
|
232
|
+
destination_buffer, destination_length_ptr, destination_buffer_length);
|
233
|
+
|
234
|
+
if (ext_result != 0) {
|
235
|
+
return ext_result;
|
236
|
+
}
|
237
|
+
|
238
|
+
continue;
|
239
|
+
}
|
240
|
+
|
241
|
+
break;
|
242
|
+
}
|
243
|
+
|
244
|
+
*source_ptr += in_buffer.pos;
|
245
|
+
*source_length_ptr -= in_buffer.pos;
|
246
|
+
|
247
|
+
return 0;
|
248
|
+
}
|
249
|
+
|
250
|
+
static inline zstds_ext_result_t buffered_compressor_finish(
|
251
|
+
ZSTD_CCtx* ctx,
|
252
|
+
FILE* destination_file, uint8_t* destination_buffer, size_t* destination_length_ptr, size_t destination_buffer_length)
|
253
|
+
{
|
254
|
+
zstds_result_t result;
|
255
|
+
zstds_ext_result_t ext_result;
|
256
|
+
|
257
|
+
ZSTD_inBuffer in_buffer;
|
258
|
+
in_buffer.src = NULL;
|
259
|
+
in_buffer.size = 0;
|
260
|
+
in_buffer.pos = 0;
|
261
|
+
|
262
|
+
ZSTD_outBuffer out_buffer;
|
263
|
+
|
264
|
+
while (true) {
|
265
|
+
out_buffer.dst = destination_buffer + *destination_length_ptr;
|
266
|
+
out_buffer.size = destination_buffer_length - *destination_length_ptr;
|
267
|
+
out_buffer.pos = 0;
|
268
|
+
|
269
|
+
result = ZSTD_compressStream2(ctx, &out_buffer, &in_buffer, ZSTD_e_end);
|
270
|
+
if (ZSTD_isError(result)) {
|
271
|
+
return zstds_ext_get_error(ZSTD_getErrorCode(result));
|
272
|
+
}
|
273
|
+
|
274
|
+
*destination_length_ptr += out_buffer.pos;
|
275
|
+
|
276
|
+
if (result != 0) {
|
277
|
+
ext_result = flush_destination_buffer(
|
278
|
+
destination_file,
|
279
|
+
destination_buffer, destination_length_ptr, destination_buffer_length);
|
280
|
+
|
281
|
+
if (ext_result != 0) {
|
282
|
+
return ext_result;
|
283
|
+
}
|
284
|
+
|
285
|
+
continue;
|
286
|
+
}
|
287
|
+
|
288
|
+
break;
|
289
|
+
}
|
290
|
+
|
291
|
+
return 0;
|
292
|
+
}
|
293
|
+
|
294
|
+
static inline zstds_ext_result_t compress(
|
295
|
+
ZSTD_CCtx* ctx,
|
296
|
+
FILE* source_file, uint8_t* source_buffer, size_t source_buffer_length,
|
297
|
+
FILE* destination_file, uint8_t* destination_buffer, size_t destination_buffer_length)
|
298
|
+
{
|
299
|
+
zstds_ext_result_t ext_result;
|
300
|
+
|
301
|
+
const uint8_t* source = source_buffer;
|
302
|
+
size_t source_length = 0;
|
303
|
+
size_t destination_length = 0;
|
304
|
+
|
305
|
+
BUFFERED_READ_SOURCE(
|
306
|
+
buffered_compress,
|
307
|
+
ctx,
|
308
|
+
&source, &source_length,
|
309
|
+
destination_file, destination_buffer, &destination_length, destination_buffer_length);
|
310
|
+
|
311
|
+
ext_result = buffered_compressor_finish(
|
312
|
+
ctx,
|
313
|
+
destination_file, destination_buffer, &destination_length, destination_buffer_length);
|
314
|
+
|
315
|
+
if (ext_result != 0) {
|
316
|
+
return ext_result;
|
317
|
+
}
|
318
|
+
|
319
|
+
return write_remaining_destination(destination_file, destination_buffer, destination_length);
|
320
|
+
}
|
321
|
+
|
322
|
+
VALUE zstds_ext_compress_io(VALUE ZSTDS_EXT_UNUSED(self), VALUE source, VALUE destination, VALUE options)
|
323
|
+
{
|
324
|
+
GET_FILE(source);
|
325
|
+
GET_FILE(destination);
|
326
|
+
Check_Type(options, T_HASH);
|
327
|
+
ZSTDS_EXT_GET_COMPRESSOR_OPTIONS(options);
|
328
|
+
ZSTDS_EXT_GET_BUFFER_LENGTH_OPTION(options, source_buffer_length);
|
329
|
+
ZSTDS_EXT_GET_BUFFER_LENGTH_OPTION(options, destination_buffer_length);
|
330
|
+
|
331
|
+
ZSTD_CCtx* ctx = ZSTD_createCCtx();
|
332
|
+
if (ctx == NULL) {
|
333
|
+
zstds_ext_raise_error(ZSTDS_EXT_ERROR_ALLOCATE_FAILED);
|
334
|
+
}
|
335
|
+
|
336
|
+
zstds_ext_result_t ext_result = zstds_ext_set_compressor_options(ctx, &compressor_options);
|
337
|
+
if (ext_result != 0) {
|
338
|
+
ZSTD_freeCCtx(ctx);
|
339
|
+
zstds_ext_raise_error(ext_result);
|
340
|
+
}
|
341
|
+
|
342
|
+
if (source_buffer_length == 0) {
|
343
|
+
source_buffer_length = ZSTD_CStreamInSize();
|
344
|
+
}
|
345
|
+
if (destination_buffer_length == 0) {
|
346
|
+
destination_buffer_length = ZSTD_CStreamOutSize();
|
347
|
+
}
|
348
|
+
|
349
|
+
uint8_t* source_buffer;
|
350
|
+
uint8_t* destination_buffer;
|
351
|
+
|
352
|
+
ext_result = create_buffers(
|
353
|
+
&source_buffer, source_buffer_length,
|
354
|
+
&destination_buffer, destination_buffer_length);
|
355
|
+
|
356
|
+
if (ext_result != 0) {
|
357
|
+
ZSTD_freeCCtx(ctx);
|
358
|
+
zstds_ext_raise_error(ext_result);
|
359
|
+
}
|
360
|
+
|
361
|
+
ext_result = compress(
|
362
|
+
ctx,
|
363
|
+
source_file, source_buffer, source_buffer_length,
|
364
|
+
destination_file, destination_buffer, destination_buffer_length);
|
365
|
+
|
366
|
+
free(source_buffer);
|
367
|
+
free(destination_buffer);
|
368
|
+
ZSTD_freeCCtx(ctx);
|
369
|
+
|
370
|
+
if (ext_result != 0) {
|
371
|
+
zstds_ext_raise_error(ext_result);
|
372
|
+
}
|
373
|
+
|
374
|
+
// Ruby itself won't flush stdio file before closing fd, flush is required.
|
375
|
+
fflush(destination_file);
|
376
|
+
|
377
|
+
return Qnil;
|
378
|
+
}
|
379
|
+
|
380
|
+
// -- decompress --
|
381
|
+
|
382
|
+
static inline zstds_ext_result_t buffered_decompress(
|
383
|
+
ZSTD_DCtx* ctx,
|
384
|
+
const uint8_t** source_ptr, size_t* source_length_ptr,
|
385
|
+
FILE* destination_file, uint8_t* destination_buffer, size_t* destination_length_ptr, size_t destination_buffer_length)
|
386
|
+
{
|
387
|
+
zstds_result_t result;
|
388
|
+
zstds_ext_result_t ext_result;
|
389
|
+
|
390
|
+
ZSTD_inBuffer in_buffer;
|
391
|
+
in_buffer.src = *source_ptr;
|
392
|
+
in_buffer.size = *source_length_ptr;
|
393
|
+
in_buffer.pos = 0;
|
394
|
+
|
395
|
+
ZSTD_outBuffer out_buffer;
|
396
|
+
|
397
|
+
while (true) {
|
398
|
+
out_buffer.dst = destination_buffer + *destination_length_ptr;
|
399
|
+
out_buffer.size = destination_buffer_length - *destination_length_ptr;
|
400
|
+
out_buffer.pos = 0;
|
401
|
+
|
402
|
+
result = ZSTD_decompressStream(ctx, &out_buffer, &in_buffer);
|
403
|
+
if (ZSTD_isError(result)) {
|
404
|
+
return zstds_ext_get_error(ZSTD_getErrorCode(result));
|
405
|
+
}
|
406
|
+
|
407
|
+
*destination_length_ptr += out_buffer.pos;
|
408
|
+
|
409
|
+
if (*destination_length_ptr == destination_buffer_length) {
|
410
|
+
ext_result = flush_destination_buffer(
|
411
|
+
destination_file,
|
412
|
+
destination_buffer, destination_length_ptr, destination_buffer_length);
|
413
|
+
|
414
|
+
if (ext_result != 0) {
|
415
|
+
return ext_result;
|
416
|
+
}
|
417
|
+
|
418
|
+
continue;
|
419
|
+
}
|
420
|
+
|
421
|
+
break;
|
422
|
+
}
|
423
|
+
|
424
|
+
*source_ptr += in_buffer.pos;
|
425
|
+
*source_length_ptr -= in_buffer.pos;
|
426
|
+
|
427
|
+
return 0;
|
428
|
+
}
|
429
|
+
|
430
|
+
static inline zstds_ext_result_t decompress(
|
431
|
+
ZSTD_DCtx* ctx,
|
432
|
+
FILE* source_file, uint8_t* source_buffer, size_t source_buffer_length,
|
433
|
+
FILE* destination_file, uint8_t* destination_buffer, size_t destination_buffer_length)
|
434
|
+
{
|
435
|
+
zstds_ext_result_t ext_result;
|
436
|
+
|
437
|
+
const uint8_t* source = source_buffer;
|
438
|
+
size_t source_length = 0;
|
439
|
+
size_t destination_length = 0;
|
440
|
+
|
441
|
+
BUFFERED_READ_SOURCE(
|
442
|
+
buffered_decompress,
|
443
|
+
ctx,
|
444
|
+
&source, &source_length,
|
445
|
+
destination_file, destination_buffer, &destination_length, destination_buffer_length);
|
446
|
+
|
447
|
+
return write_remaining_destination(destination_file, destination_buffer, destination_length);
|
448
|
+
}
|
449
|
+
|
450
|
+
VALUE zstds_ext_decompress_io(VALUE ZSTDS_EXT_UNUSED(self), VALUE source, VALUE destination, VALUE options)
|
451
|
+
{
|
452
|
+
GET_FILE(source);
|
453
|
+
GET_FILE(destination);
|
454
|
+
Check_Type(options, T_HASH);
|
455
|
+
ZSTDS_EXT_GET_DECOMPRESSOR_OPTIONS(options);
|
456
|
+
ZSTDS_EXT_GET_BUFFER_LENGTH_OPTION(options, source_buffer_length);
|
457
|
+
ZSTDS_EXT_GET_BUFFER_LENGTH_OPTION(options, destination_buffer_length);
|
458
|
+
|
459
|
+
ZSTD_DCtx* ctx = ZSTD_createDCtx();
|
460
|
+
if (ctx == NULL) {
|
461
|
+
zstds_ext_raise_error(ZSTDS_EXT_ERROR_ALLOCATE_FAILED);
|
462
|
+
}
|
463
|
+
|
464
|
+
zstds_ext_result_t ext_result = zstds_ext_set_decompressor_options(ctx, &decompressor_options);
|
465
|
+
if (ext_result != 0) {
|
466
|
+
ZSTD_freeDCtx(ctx);
|
467
|
+
zstds_ext_raise_error(ext_result);
|
468
|
+
}
|
469
|
+
|
470
|
+
if (source_buffer_length == 0) {
|
471
|
+
source_buffer_length = ZSTD_DStreamInSize();
|
472
|
+
}
|
473
|
+
if (destination_buffer_length == 0) {
|
474
|
+
destination_buffer_length = ZSTD_DStreamOutSize();
|
475
|
+
}
|
476
|
+
|
477
|
+
uint8_t* source_buffer;
|
478
|
+
uint8_t* destination_buffer;
|
479
|
+
|
480
|
+
ext_result = create_buffers(
|
481
|
+
&source_buffer, source_buffer_length,
|
482
|
+
&destination_buffer, destination_buffer_length);
|
483
|
+
|
484
|
+
if (ext_result != 0) {
|
485
|
+
ZSTD_freeDCtx(ctx);
|
486
|
+
zstds_ext_raise_error(ext_result);
|
487
|
+
}
|
488
|
+
|
489
|
+
ext_result = decompress(
|
490
|
+
ctx,
|
491
|
+
source_file, source_buffer, source_buffer_length,
|
492
|
+
destination_file, destination_buffer, destination_buffer_length);
|
493
|
+
|
494
|
+
free(source_buffer);
|
495
|
+
free(destination_buffer);
|
496
|
+
ZSTD_freeDCtx(ctx);
|
497
|
+
|
498
|
+
if (ext_result != 0) {
|
499
|
+
zstds_ext_raise_error(ext_result);
|
500
|
+
}
|
501
|
+
|
502
|
+
// Ruby itself won't flush stdio file before closing fd, flush is required.
|
503
|
+
fflush(destination_file);
|
504
|
+
|
505
|
+
return Qnil;
|
506
|
+
}
|
507
|
+
|
508
|
+
void zstds_ext_io_exports(VALUE root_module)
|
509
|
+
{
|
510
|
+
rb_define_module_function(root_module, "_native_compress_io", RUBY_METHOD_FUNC(zstds_ext_compress_io), 3);
|
511
|
+
rb_define_module_function(root_module, "_native_decompress_io", RUBY_METHOD_FUNC(zstds_ext_decompress_io), 3);
|
512
|
+
}
|