vibe_zstd 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.
Files changed (109) hide show
  1. checksums.yaml +7 -0
  2. data/.standard.yml +3 -0
  3. data/CHANGELOG.md +22 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +978 -0
  6. data/Rakefile +20 -0
  7. data/benchmark/README.md +198 -0
  8. data/benchmark/compression_levels.rb +99 -0
  9. data/benchmark/context_reuse.rb +174 -0
  10. data/benchmark/decompression_speed_by_level.rb +65 -0
  11. data/benchmark/dictionary_training.rb +182 -0
  12. data/benchmark/dictionary_usage.rb +121 -0
  13. data/benchmark/for_readme.rb +157 -0
  14. data/benchmark/generate_fixture.rb +82 -0
  15. data/benchmark/helpers.rb +237 -0
  16. data/benchmark/multithreading.rb +105 -0
  17. data/benchmark/run_all.rb +150 -0
  18. data/benchmark/streaming.rb +154 -0
  19. data/ext/vibe_zstd/Makefile +270 -0
  20. data/ext/vibe_zstd/cctx.c +565 -0
  21. data/ext/vibe_zstd/dctx.c +493 -0
  22. data/ext/vibe_zstd/dict.c +587 -0
  23. data/ext/vibe_zstd/extconf.rb +52 -0
  24. data/ext/vibe_zstd/frames.c +132 -0
  25. data/ext/vibe_zstd/libzstd/LICENSE +30 -0
  26. data/ext/vibe_zstd/libzstd/common/allocations.h +55 -0
  27. data/ext/vibe_zstd/libzstd/common/bits.h +205 -0
  28. data/ext/vibe_zstd/libzstd/common/bitstream.h +454 -0
  29. data/ext/vibe_zstd/libzstd/common/compiler.h +464 -0
  30. data/ext/vibe_zstd/libzstd/common/cpu.h +249 -0
  31. data/ext/vibe_zstd/libzstd/common/debug.c +30 -0
  32. data/ext/vibe_zstd/libzstd/common/debug.h +107 -0
  33. data/ext/vibe_zstd/libzstd/common/entropy_common.c +340 -0
  34. data/ext/vibe_zstd/libzstd/common/error_private.c +64 -0
  35. data/ext/vibe_zstd/libzstd/common/error_private.h +158 -0
  36. data/ext/vibe_zstd/libzstd/common/fse.h +625 -0
  37. data/ext/vibe_zstd/libzstd/common/fse_decompress.c +315 -0
  38. data/ext/vibe_zstd/libzstd/common/huf.h +277 -0
  39. data/ext/vibe_zstd/libzstd/common/mem.h +422 -0
  40. data/ext/vibe_zstd/libzstd/common/pool.c +371 -0
  41. data/ext/vibe_zstd/libzstd/common/pool.h +81 -0
  42. data/ext/vibe_zstd/libzstd/common/portability_macros.h +171 -0
  43. data/ext/vibe_zstd/libzstd/common/threading.c +182 -0
  44. data/ext/vibe_zstd/libzstd/common/threading.h +142 -0
  45. data/ext/vibe_zstd/libzstd/common/xxhash.c +18 -0
  46. data/ext/vibe_zstd/libzstd/common/xxhash.h +7094 -0
  47. data/ext/vibe_zstd/libzstd/common/zstd_common.c +48 -0
  48. data/ext/vibe_zstd/libzstd/common/zstd_deps.h +123 -0
  49. data/ext/vibe_zstd/libzstd/common/zstd_internal.h +324 -0
  50. data/ext/vibe_zstd/libzstd/common/zstd_trace.h +156 -0
  51. data/ext/vibe_zstd/libzstd/compress/clevels.h +134 -0
  52. data/ext/vibe_zstd/libzstd/compress/fse_compress.c +625 -0
  53. data/ext/vibe_zstd/libzstd/compress/hist.c +191 -0
  54. data/ext/vibe_zstd/libzstd/compress/hist.h +82 -0
  55. data/ext/vibe_zstd/libzstd/compress/huf_compress.c +1464 -0
  56. data/ext/vibe_zstd/libzstd/compress/zstd_compress.c +7843 -0
  57. data/ext/vibe_zstd/libzstd/compress/zstd_compress_internal.h +1636 -0
  58. data/ext/vibe_zstd/libzstd/compress/zstd_compress_literals.c +235 -0
  59. data/ext/vibe_zstd/libzstd/compress/zstd_compress_literals.h +39 -0
  60. data/ext/vibe_zstd/libzstd/compress/zstd_compress_sequences.c +442 -0
  61. data/ext/vibe_zstd/libzstd/compress/zstd_compress_sequences.h +55 -0
  62. data/ext/vibe_zstd/libzstd/compress/zstd_compress_superblock.c +688 -0
  63. data/ext/vibe_zstd/libzstd/compress/zstd_compress_superblock.h +32 -0
  64. data/ext/vibe_zstd/libzstd/compress/zstd_cwksp.h +765 -0
  65. data/ext/vibe_zstd/libzstd/compress/zstd_double_fast.c +778 -0
  66. data/ext/vibe_zstd/libzstd/compress/zstd_double_fast.h +42 -0
  67. data/ext/vibe_zstd/libzstd/compress/zstd_fast.c +985 -0
  68. data/ext/vibe_zstd/libzstd/compress/zstd_fast.h +30 -0
  69. data/ext/vibe_zstd/libzstd/compress/zstd_lazy.c +2199 -0
  70. data/ext/vibe_zstd/libzstd/compress/zstd_lazy.h +193 -0
  71. data/ext/vibe_zstd/libzstd/compress/zstd_ldm.c +745 -0
  72. data/ext/vibe_zstd/libzstd/compress/zstd_ldm.h +109 -0
  73. data/ext/vibe_zstd/libzstd/compress/zstd_ldm_geartab.h +106 -0
  74. data/ext/vibe_zstd/libzstd/compress/zstd_opt.c +1580 -0
  75. data/ext/vibe_zstd/libzstd/compress/zstd_opt.h +72 -0
  76. data/ext/vibe_zstd/libzstd/compress/zstd_preSplit.c +238 -0
  77. data/ext/vibe_zstd/libzstd/compress/zstd_preSplit.h +33 -0
  78. data/ext/vibe_zstd/libzstd/compress/zstdmt_compress.c +1923 -0
  79. data/ext/vibe_zstd/libzstd/compress/zstdmt_compress.h +102 -0
  80. data/ext/vibe_zstd/libzstd/decompress/huf_decompress.c +1944 -0
  81. data/ext/vibe_zstd/libzstd/decompress/huf_decompress_amd64.S +602 -0
  82. data/ext/vibe_zstd/libzstd/decompress/zstd_ddict.c +244 -0
  83. data/ext/vibe_zstd/libzstd/decompress/zstd_ddict.h +44 -0
  84. data/ext/vibe_zstd/libzstd/decompress/zstd_decompress.c +2410 -0
  85. data/ext/vibe_zstd/libzstd/decompress/zstd_decompress_block.c +2209 -0
  86. data/ext/vibe_zstd/libzstd/decompress/zstd_decompress_block.h +73 -0
  87. data/ext/vibe_zstd/libzstd/decompress/zstd_decompress_internal.h +240 -0
  88. data/ext/vibe_zstd/libzstd/deprecated/zbuff.h +214 -0
  89. data/ext/vibe_zstd/libzstd/deprecated/zbuff_common.c +26 -0
  90. data/ext/vibe_zstd/libzstd/deprecated/zbuff_compress.c +167 -0
  91. data/ext/vibe_zstd/libzstd/deprecated/zbuff_decompress.c +77 -0
  92. data/ext/vibe_zstd/libzstd/dictBuilder/cover.c +1302 -0
  93. data/ext/vibe_zstd/libzstd/dictBuilder/cover.h +152 -0
  94. data/ext/vibe_zstd/libzstd/dictBuilder/divsufsort.c +1913 -0
  95. data/ext/vibe_zstd/libzstd/dictBuilder/divsufsort.h +57 -0
  96. data/ext/vibe_zstd/libzstd/dictBuilder/fastcover.c +766 -0
  97. data/ext/vibe_zstd/libzstd/dictBuilder/zdict.c +1133 -0
  98. data/ext/vibe_zstd/libzstd/zdict.h +481 -0
  99. data/ext/vibe_zstd/libzstd/zstd.h +3198 -0
  100. data/ext/vibe_zstd/libzstd/zstd_errors.h +107 -0
  101. data/ext/vibe_zstd/streaming.c +410 -0
  102. data/ext/vibe_zstd/vibe_zstd.c +293 -0
  103. data/ext/vibe_zstd/vibe_zstd.h +56 -0
  104. data/ext/vibe_zstd/vibe_zstd_internal.h +27 -0
  105. data/lib/vibe_zstd/constants.rb +67 -0
  106. data/lib/vibe_zstd/version.rb +5 -0
  107. data/lib/vibe_zstd.rb +255 -0
  108. data/sig/vibe_zstd.rbs +76 -0
  109. metadata +179 -0
@@ -0,0 +1,107 @@
1
+ /*
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under both the BSD-style license (found in the
6
+ * LICENSE file in the root directory of this source tree) and the GPLv2 (found
7
+ * in the COPYING file in the root directory of this source tree).
8
+ * You may select, at your option, one of the above-listed licenses.
9
+ */
10
+
11
+ #ifndef ZSTD_ERRORS_H_398273423
12
+ #define ZSTD_ERRORS_H_398273423
13
+
14
+ #if defined (__cplusplus)
15
+ extern "C" {
16
+ #endif
17
+
18
+ /* ===== ZSTDERRORLIB_API : control library symbols visibility ===== */
19
+ #ifndef ZSTDERRORLIB_VISIBLE
20
+ /* Backwards compatibility with old macro name */
21
+ # ifdef ZSTDERRORLIB_VISIBILITY
22
+ # define ZSTDERRORLIB_VISIBLE ZSTDERRORLIB_VISIBILITY
23
+ # elif defined(__GNUC__) && (__GNUC__ >= 4) && !defined(__MINGW32__)
24
+ # define ZSTDERRORLIB_VISIBLE __attribute__ ((visibility ("default")))
25
+ # else
26
+ # define ZSTDERRORLIB_VISIBLE
27
+ # endif
28
+ #endif
29
+
30
+ #ifndef ZSTDERRORLIB_HIDDEN
31
+ # if defined(__GNUC__) && (__GNUC__ >= 4) && !defined(__MINGW32__)
32
+ # define ZSTDERRORLIB_HIDDEN __attribute__ ((visibility ("hidden")))
33
+ # else
34
+ # define ZSTDERRORLIB_HIDDEN
35
+ # endif
36
+ #endif
37
+
38
+ #if defined(ZSTD_DLL_EXPORT) && (ZSTD_DLL_EXPORT==1)
39
+ # define ZSTDERRORLIB_API __declspec(dllexport) ZSTDERRORLIB_VISIBLE
40
+ #elif defined(ZSTD_DLL_IMPORT) && (ZSTD_DLL_IMPORT==1)
41
+ # define ZSTDERRORLIB_API __declspec(dllimport) ZSTDERRORLIB_VISIBLE /* It isn't required but allows to generate better code, saving a function pointer load from the IAT and an indirect jump.*/
42
+ #else
43
+ # define ZSTDERRORLIB_API ZSTDERRORLIB_VISIBLE
44
+ #endif
45
+
46
+ /*-*********************************************
47
+ * Error codes list
48
+ *-*********************************************
49
+ * Error codes _values_ are pinned down since v1.3.1 only.
50
+ * Therefore, don't rely on values if you may link to any version < v1.3.1.
51
+ *
52
+ * Only values < 100 are considered stable.
53
+ *
54
+ * note 1 : this API shall be used with static linking only.
55
+ * dynamic linking is not yet officially supported.
56
+ * note 2 : Prefer relying on the enum than on its value whenever possible
57
+ * This is the only supported way to use the error list < v1.3.1
58
+ * note 3 : ZSTD_isError() is always correct, whatever the library version.
59
+ **********************************************/
60
+ typedef enum {
61
+ ZSTD_error_no_error = 0,
62
+ ZSTD_error_GENERIC = 1,
63
+ ZSTD_error_prefix_unknown = 10,
64
+ ZSTD_error_version_unsupported = 12,
65
+ ZSTD_error_frameParameter_unsupported = 14,
66
+ ZSTD_error_frameParameter_windowTooLarge = 16,
67
+ ZSTD_error_corruption_detected = 20,
68
+ ZSTD_error_checksum_wrong = 22,
69
+ ZSTD_error_literals_headerWrong = 24,
70
+ ZSTD_error_dictionary_corrupted = 30,
71
+ ZSTD_error_dictionary_wrong = 32,
72
+ ZSTD_error_dictionaryCreation_failed = 34,
73
+ ZSTD_error_parameter_unsupported = 40,
74
+ ZSTD_error_parameter_combination_unsupported = 41,
75
+ ZSTD_error_parameter_outOfBound = 42,
76
+ ZSTD_error_tableLog_tooLarge = 44,
77
+ ZSTD_error_maxSymbolValue_tooLarge = 46,
78
+ ZSTD_error_maxSymbolValue_tooSmall = 48,
79
+ ZSTD_error_cannotProduce_uncompressedBlock = 49,
80
+ ZSTD_error_stabilityCondition_notRespected = 50,
81
+ ZSTD_error_stage_wrong = 60,
82
+ ZSTD_error_init_missing = 62,
83
+ ZSTD_error_memory_allocation = 64,
84
+ ZSTD_error_workSpace_tooSmall= 66,
85
+ ZSTD_error_dstSize_tooSmall = 70,
86
+ ZSTD_error_srcSize_wrong = 72,
87
+ ZSTD_error_dstBuffer_null = 74,
88
+ ZSTD_error_noForwardProgress_destFull = 80,
89
+ ZSTD_error_noForwardProgress_inputEmpty = 82,
90
+ /* following error codes are __NOT STABLE__, they can be removed or changed in future versions */
91
+ ZSTD_error_frameIndex_tooLarge = 100,
92
+ ZSTD_error_seekableIO = 102,
93
+ ZSTD_error_dstBuffer_wrong = 104,
94
+ ZSTD_error_srcBuffer_wrong = 105,
95
+ ZSTD_error_sequenceProducer_failed = 106,
96
+ ZSTD_error_externalSequences_invalid = 107,
97
+ ZSTD_error_maxCode = 120 /* never EVER use this value directly, it can change in future versions! Use ZSTD_isError() instead */
98
+ } ZSTD_ErrorCode;
99
+
100
+ ZSTDERRORLIB_API const char* ZSTD_getErrorString(ZSTD_ErrorCode code); /**< Same as ZSTD_getErrorName, but using a `ZSTD_ErrorCode` enum argument */
101
+
102
+
103
+ #if defined (__cplusplus)
104
+ }
105
+ #endif
106
+
107
+ #endif /* ZSTD_ERRORS_H_398273423 */
@@ -0,0 +1,410 @@
1
+ // Streaming implementation for VibeZstd
2
+ #include "vibe_zstd_internal.h"
3
+
4
+ // Forward declarations
5
+ static VALUE vibe_zstd_writer_initialize(int argc, VALUE *argv, VALUE self);
6
+ static VALUE vibe_zstd_writer_write(VALUE self, VALUE data);
7
+ static VALUE vibe_zstd_writer_flush(VALUE self);
8
+ static VALUE vibe_zstd_writer_finish(VALUE self);
9
+ static VALUE vibe_zstd_reader_initialize(int argc, VALUE *argv, VALUE self);
10
+ static VALUE vibe_zstd_reader_read(int argc, VALUE *argv, VALUE self);
11
+ static VALUE vibe_zstd_reader_eof(VALUE self);
12
+
13
+ // TypedData types - defined in vibe_zstd.c
14
+ extern rb_data_type_t vibe_zstd_cstream_type;
15
+ extern rb_data_type_t vibe_zstd_dstream_type;
16
+
17
+ // CompressWriter implementation
18
+ // Wraps ZSTD streaming compression to write compressed data to an IO object
19
+ static VALUE
20
+ vibe_zstd_writer_initialize(int argc, VALUE *argv, VALUE self) {
21
+ VALUE io, options;
22
+ rb_scan_args(argc, argv, "11", &io, &options);
23
+
24
+ vibe_zstd_cstream* cstream;
25
+ TypedData_Get_Struct(self, vibe_zstd_cstream, &vibe_zstd_cstream_type, cstream);
26
+
27
+ // Validate IO object responds to write (duck typing)
28
+ if (!rb_respond_to(io, rb_intern("write"))) {
29
+ rb_raise(rb_eTypeError, "IO object must respond to write");
30
+ }
31
+
32
+ // Store IO object
33
+ cstream->io = io;
34
+ rb_ivar_set(self, rb_intern("@io"), io);
35
+
36
+ // Parse options
37
+ int level = 3; // default compression level
38
+ VALUE dict = Qnil;
39
+ unsigned long long pledged_size = ZSTD_CONTENTSIZE_UNKNOWN;
40
+
41
+ if (!NIL_P(options)) {
42
+ Check_Type(options, T_HASH);
43
+ VALUE v_level = rb_hash_aref(options, ID2SYM(rb_intern("level")));
44
+ if (!NIL_P(v_level)) {
45
+ level = NUM2INT(v_level);
46
+ }
47
+ dict = rb_hash_aref(options, ID2SYM(rb_intern("dict")));
48
+
49
+ VALUE v_pledged = rb_hash_aref(options, ID2SYM(rb_intern("pledged_size")));
50
+ if (!NIL_P(v_pledged)) {
51
+ pledged_size = NUM2ULL(v_pledged);
52
+ }
53
+ }
54
+
55
+ // Create compression context (CStream and CCtx are the same since v1.3.0)
56
+ cstream->cstream = ZSTD_createCStream();
57
+ if (!cstream->cstream) {
58
+ rb_raise(rb_eRuntimeError, "Failed to create compression stream");
59
+ }
60
+
61
+ // Reset context for streaming and set compression level
62
+ size_t result = ZSTD_CCtx_reset((ZSTD_CCtx*)cstream->cstream, ZSTD_reset_session_only);
63
+ if (ZSTD_isError(result)) {
64
+ rb_raise(rb_eRuntimeError, "Failed to reset compression context: %s", ZSTD_getErrorName(result));
65
+ }
66
+
67
+ result = ZSTD_CCtx_setParameter((ZSTD_CCtx*)cstream->cstream, ZSTD_c_compressionLevel, level);
68
+ if (ZSTD_isError(result)) {
69
+ rb_raise(rb_eRuntimeError, "Failed to set compression level: %s", ZSTD_getErrorName(result));
70
+ }
71
+
72
+ // Set pledged source size if provided
73
+ if (pledged_size != ZSTD_CONTENTSIZE_UNKNOWN) {
74
+ result = ZSTD_CCtx_setPledgedSrcSize((ZSTD_CCtx*)cstream->cstream, pledged_size);
75
+ if (ZSTD_isError(result)) {
76
+ rb_raise(rb_eRuntimeError, "Failed to set pledged source size: %s", ZSTD_getErrorName(result));
77
+ }
78
+ }
79
+
80
+ // Set dictionary if provided
81
+ if (!NIL_P(dict)) {
82
+ vibe_zstd_cdict* cdict_obj;
83
+ TypedData_Get_Struct(dict, vibe_zstd_cdict, &vibe_zstd_cdict_type, cdict_obj);
84
+ result = ZSTD_CCtx_refCDict((ZSTD_CCtx*)cstream->cstream, cdict_obj->cdict);
85
+ if (ZSTD_isError(result)) {
86
+ rb_raise(rb_eRuntimeError, "Failed to set dictionary: %s", ZSTD_getErrorName(result));
87
+ }
88
+ }
89
+
90
+ return self;
91
+ }
92
+
93
+ static VALUE
94
+ vibe_zstd_writer_write(VALUE self, VALUE data) {
95
+ Check_Type(data, T_STRING);
96
+
97
+ vibe_zstd_cstream* cstream;
98
+ TypedData_Get_Struct(self, vibe_zstd_cstream, &vibe_zstd_cstream_type, cstream);
99
+
100
+ // Input buffer: pos advances as ZSTD consumes data
101
+ ZSTD_inBuffer input = {
102
+ .src = RSTRING_PTR(data),
103
+ .size = RSTRING_LEN(data),
104
+ .pos = 0
105
+ };
106
+
107
+ size_t outBufferSize = ZSTD_CStreamOutSize();
108
+ VALUE outBuffer = rb_str_buf_new(outBufferSize);
109
+
110
+ // Process all input data in chunks
111
+ while (input.pos < input.size) {
112
+ ZSTD_outBuffer output = {
113
+ .dst = RSTRING_PTR(outBuffer),
114
+ .size = outBufferSize,
115
+ .pos = 0
116
+ };
117
+
118
+ // ZSTD_e_continue: continue compression without flushing
119
+ // Return value is a hint for preferred input size (can be ignored)
120
+ size_t result = ZSTD_compressStream2((ZSTD_CCtx*)cstream->cstream, &output, &input, ZSTD_e_continue);
121
+ if (ZSTD_isError(result)) {
122
+ rb_raise(rb_eRuntimeError, "Compression failed: %s", ZSTD_getErrorName(result));
123
+ }
124
+
125
+ // Write any compressed output that was produced
126
+ if (output.pos > 0) {
127
+ rb_str_set_len(outBuffer, output.pos);
128
+ rb_funcall(cstream->io, rb_intern("write"), 1, outBuffer);
129
+ // No need to resize - buffer capacity remains at outBufferSize
130
+ }
131
+ }
132
+
133
+ return self;
134
+ }
135
+
136
+ static VALUE
137
+ vibe_zstd_writer_flush(VALUE self) {
138
+ vibe_zstd_cstream* cstream;
139
+ TypedData_Get_Struct(self, vibe_zstd_cstream, &vibe_zstd_cstream_type, cstream);
140
+
141
+ size_t outBufferSize = ZSTD_CStreamOutSize();
142
+ VALUE outBuffer = rb_str_buf_new(outBufferSize);
143
+
144
+ ZSTD_inBuffer input = { NULL, 0, 0 };
145
+ size_t remaining;
146
+
147
+ // ZSTD_e_flush: flush internal buffers, making all data readable
148
+ // Loop until remaining == 0 (flush complete)
149
+ do {
150
+ ZSTD_outBuffer output = {
151
+ .dst = RSTRING_PTR(outBuffer),
152
+ .size = outBufferSize,
153
+ .pos = 0
154
+ };
155
+
156
+ // Return value > 0 means more flushing needed
157
+ remaining = ZSTD_compressStream2((ZSTD_CCtx*)cstream->cstream, &output, &input, ZSTD_e_flush);
158
+ if (ZSTD_isError(remaining)) {
159
+ rb_raise(rb_eRuntimeError, "Flush failed: %s", ZSTD_getErrorName(remaining));
160
+ }
161
+
162
+ if (output.pos > 0) {
163
+ rb_str_set_len(outBuffer, output.pos);
164
+ rb_funcall(cstream->io, rb_intern("write"), 1, outBuffer);
165
+ // No need to resize - buffer capacity remains at outBufferSize
166
+ }
167
+ } while (remaining > 0);
168
+
169
+ return self;
170
+ }
171
+
172
+ static VALUE
173
+ vibe_zstd_writer_finish(VALUE self) {
174
+ vibe_zstd_cstream* cstream;
175
+ TypedData_Get_Struct(self, vibe_zstd_cstream, &vibe_zstd_cstream_type, cstream);
176
+
177
+ size_t outBufferSize = ZSTD_CStreamOutSize();
178
+ VALUE outBuffer = rb_str_buf_new(outBufferSize);
179
+
180
+ ZSTD_inBuffer input = { NULL, 0, 0 };
181
+ size_t remaining;
182
+
183
+ // ZSTD_e_end: finalize frame with checksum and epilogue
184
+ // Loop until remaining == 0 (frame complete)
185
+ do {
186
+ ZSTD_outBuffer output = {
187
+ .dst = RSTRING_PTR(outBuffer),
188
+ .size = outBufferSize,
189
+ .pos = 0
190
+ };
191
+
192
+ // Return value > 0 means more epilogue data to write
193
+ remaining = ZSTD_compressStream2((ZSTD_CCtx*)cstream->cstream, &output, &input, ZSTD_e_end);
194
+ if (ZSTD_isError(remaining)) {
195
+ rb_raise(rb_eRuntimeError, "Finish failed: %s", ZSTD_getErrorName(remaining));
196
+ }
197
+
198
+ if (output.pos > 0) {
199
+ rb_str_set_len(outBuffer, output.pos);
200
+ rb_funcall(cstream->io, rb_intern("write"), 1, outBuffer);
201
+ // No need to resize - buffer capacity remains at outBufferSize
202
+ }
203
+ } while (remaining > 0);
204
+
205
+ return self;
206
+ }
207
+
208
+ // DecompressReader implementation
209
+ // Wraps ZSTD streaming decompression to read from a compressed IO object
210
+ static VALUE
211
+ vibe_zstd_reader_initialize(int argc, VALUE *argv, VALUE self) {
212
+ VALUE io, options;
213
+ rb_scan_args(argc, argv, "11", &io, &options);
214
+
215
+ vibe_zstd_dstream* dstream;
216
+ TypedData_Get_Struct(self, vibe_zstd_dstream, &vibe_zstd_dstream_type, dstream);
217
+
218
+ // Validate IO object responds to read (duck typing)
219
+ if (!rb_respond_to(io, rb_intern("read"))) {
220
+ rb_raise(rb_eTypeError, "IO object must respond to read");
221
+ }
222
+
223
+ // Store IO object
224
+ dstream->io = io;
225
+ rb_ivar_set(self, rb_intern("@io"), io);
226
+
227
+ // Parse options
228
+ VALUE dict = Qnil;
229
+ size_t initial_chunk_size = 0; // 0 = use default ZSTD_DStreamOutSize()
230
+ if (!NIL_P(options)) {
231
+ Check_Type(options, T_HASH);
232
+ dict = rb_hash_aref(options, ID2SYM(rb_intern("dict")));
233
+
234
+ VALUE v_chunk_size = rb_hash_aref(options, ID2SYM(rb_intern("initial_chunk_size")));
235
+ if (!NIL_P(v_chunk_size)) {
236
+ initial_chunk_size = NUM2SIZET(v_chunk_size);
237
+ if (initial_chunk_size == 0) {
238
+ rb_raise(rb_eArgError, "initial_chunk_size must be greater than 0");
239
+ }
240
+ }
241
+ }
242
+
243
+ // Create decompression context (DStream and DCtx are the same since v1.3.0)
244
+ dstream->dstream = ZSTD_createDStream();
245
+ if (!dstream->dstream) {
246
+ rb_raise(rb_eRuntimeError, "Failed to create decompression stream");
247
+ }
248
+
249
+ // Reset context for streaming
250
+ size_t result = ZSTD_DCtx_reset((ZSTD_DCtx*)dstream->dstream, ZSTD_reset_session_only);
251
+ if (ZSTD_isError(result)) {
252
+ rb_raise(rb_eRuntimeError, "Failed to reset decompression context: %s", ZSTD_getErrorName(result));
253
+ }
254
+
255
+ // Set dictionary if provided
256
+ if (!NIL_P(dict)) {
257
+ vibe_zstd_ddict* ddict_obj;
258
+ TypedData_Get_Struct(dict, vibe_zstd_ddict, &vibe_zstd_ddict_type, ddict_obj);
259
+ result = ZSTD_DCtx_refDDict((ZSTD_DCtx*)dstream->dstream, ddict_obj->ddict);
260
+ if (ZSTD_isError(result)) {
261
+ rb_raise(rb_eRuntimeError, "Failed to set dictionary: %s", ZSTD_getErrorName(result));
262
+ }
263
+ }
264
+
265
+ // Initialize input buffer management
266
+ dstream->input_data = rb_str_new(NULL, 0);
267
+ dstream->input.src = NULL;
268
+ dstream->input.size = 0;
269
+ dstream->input.pos = 0;
270
+ dstream->eof = 0;
271
+ dstream->initial_chunk_size = initial_chunk_size;
272
+
273
+ return self;
274
+ }
275
+
276
+ // DecompressReader read - Read decompressed data from stream
277
+ //
278
+ // Handles streaming decompression with buffered input management:
279
+ // - Requested size: Reads up to specified number of bytes
280
+ // - No size (nil): Reads one chunk (default: ZSTD_DStreamOutSize ~128KB)
281
+ //
282
+ // Buffer management:
283
+ // - Maintains internal compressed input buffer that refills from IO as needed
284
+ // - Calls ZSTD_decompressStream incrementally to produce output
285
+ // - Tracks EOF state based on IO exhaustion and frame completion
286
+ //
287
+ // EOF handling:
288
+ // - Returns nil when no more data available
289
+ // - Sets eof flag when: IO returns nil, frame complete (ret==0), or no progress made
290
+ //
291
+ // This implements proper streaming semantics for incremental decompression
292
+ // of arbitrarily large files without loading everything into memory.
293
+ static VALUE
294
+ vibe_zstd_reader_read(int argc, VALUE *argv, VALUE self) {
295
+ VALUE size_arg;
296
+ rb_scan_args(argc, argv, "01", &size_arg);
297
+
298
+ vibe_zstd_dstream* dstream;
299
+ TypedData_Get_Struct(self, vibe_zstd_dstream, &vibe_zstd_dstream_type, dstream);
300
+
301
+ if (dstream->eof) {
302
+ return Qnil;
303
+ }
304
+
305
+ // Unbounded reads use configurable chunk size (defaults to ZSTD_DStreamOutSize() ~128KB)
306
+ // This provides chunked streaming behavior for true streaming use cases
307
+ size_t default_chunk_size = (dstream->initial_chunk_size > 0) ? dstream->initial_chunk_size : ZSTD_DStreamOutSize();
308
+ size_t requested_size = NIL_P(size_arg) ? default_chunk_size : NUM2SIZET(size_arg);
309
+ size_t inBufferSize = ZSTD_DStreamInSize();
310
+
311
+ // Preallocate buffer for requested size
312
+ VALUE result = rb_str_buf_new(requested_size);
313
+
314
+ size_t total_read = 0;
315
+ int made_progress = 0;
316
+
317
+ while (total_read < requested_size) {
318
+ // Refill input buffer when all compressed data consumed
319
+ if (dstream->input.pos >= dstream->input.size) {
320
+ VALUE chunk = rb_funcall(dstream->io, rb_intern("read"), 1, SIZET2NUM(inBufferSize));
321
+ if (NIL_P(chunk)) {
322
+ dstream->eof = 1;
323
+ if (total_read == 0 && !made_progress) {
324
+ return Qnil;
325
+ }
326
+ break;
327
+ }
328
+
329
+ // Reset input buffer with new data
330
+ dstream->input_data = chunk;
331
+ dstream->input.src = RSTRING_PTR(chunk);
332
+ dstream->input.size = RSTRING_LEN(chunk);
333
+ dstream->input.pos = 0;
334
+ }
335
+
336
+ if (dstream->input.size == 0) {
337
+ dstream->eof = 1;
338
+ break;
339
+ }
340
+
341
+ size_t space_left = requested_size - total_read;
342
+
343
+ ZSTD_outBuffer output = {
344
+ .dst = RSTRING_PTR(result) + total_read,
345
+ .size = space_left,
346
+ .pos = 0
347
+ };
348
+
349
+ // ZSTD_decompressStream advances input.pos and output.pos
350
+ // Return value: 0 = frame complete, >0 = hint for next input size, error if < 0
351
+ size_t ret = ZSTD_decompressStream(dstream->dstream, &output, &dstream->input);
352
+ if (ZSTD_isError(ret)) {
353
+ rb_raise(rb_eRuntimeError, "Decompression failed: %s", ZSTD_getErrorName(ret));
354
+ }
355
+
356
+ if (output.pos > 0) {
357
+ total_read += output.pos;
358
+ made_progress = 1;
359
+ }
360
+
361
+ // Exit when we've read enough data
362
+ if (total_read >= requested_size) {
363
+ break;
364
+ }
365
+
366
+ // ret == 0 signals end of current frame
367
+ if (ret == 0) {
368
+ dstream->eof = 1;
369
+ break;
370
+ }
371
+
372
+ // No output produced: need more input
373
+ if (output.pos == 0) {
374
+ continue;
375
+ }
376
+ }
377
+
378
+ if (total_read == 0) {
379
+ dstream->eof = 1;
380
+ return Qnil;
381
+ }
382
+
383
+ rb_str_set_len(result, total_read);
384
+ return result;
385
+ }
386
+
387
+ static VALUE
388
+ vibe_zstd_reader_eof(VALUE self) {
389
+ vibe_zstd_dstream* dstream;
390
+ TypedData_Get_Struct(self, vibe_zstd_dstream, &vibe_zstd_dstream_type, dstream);
391
+ return dstream->eof ? Qtrue : Qfalse;
392
+ }
393
+
394
+ // Class initialization function called from main Init_vibe_zstd
395
+ void
396
+ vibe_zstd_streaming_init_classes(VALUE rb_cVibeZstdCompressWriter, VALUE rb_cVibeZstdDecompressReader) {
397
+ // CompressWriter setup
398
+ rb_define_alloc_func(rb_cVibeZstdCompressWriter, vibe_zstd_cstream_alloc);
399
+ rb_define_method(rb_cVibeZstdCompressWriter, "initialize", vibe_zstd_writer_initialize, -1);
400
+ rb_define_method(rb_cVibeZstdCompressWriter, "write", vibe_zstd_writer_write, 1);
401
+ rb_define_method(rb_cVibeZstdCompressWriter, "flush", vibe_zstd_writer_flush, 0);
402
+ rb_define_method(rb_cVibeZstdCompressWriter, "finish", vibe_zstd_writer_finish, 0);
403
+ rb_define_method(rb_cVibeZstdCompressWriter, "close", vibe_zstd_writer_finish, 0); // alias
404
+
405
+ // DecompressReader setup
406
+ rb_define_alloc_func(rb_cVibeZstdDecompressReader, vibe_zstd_dstream_alloc);
407
+ rb_define_method(rb_cVibeZstdDecompressReader, "initialize", vibe_zstd_reader_initialize, -1);
408
+ rb_define_method(rb_cVibeZstdDecompressReader, "read", vibe_zstd_reader_read, -1);
409
+ rb_define_method(rb_cVibeZstdDecompressReader, "eof?", vibe_zstd_reader_eof, 0);
410
+ }