brotli 0.7.0 → 0.9.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 (128) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +113 -1
  3. data/ext/brotli/brotli.c +908 -288
  4. data/ext/brotli/brotli.h +0 -5
  5. data/ext/brotli/buffer.c +43 -65
  6. data/ext/brotli/buffer.h +6 -9
  7. data/ext/brotli/extconf.rb +115 -50
  8. data/lib/brotli/reader.rb +167 -0
  9. data/lib/brotli/version.rb +1 -1
  10. data/lib/brotli/writer.rb +81 -0
  11. data/lib/brotli.rb +2 -0
  12. data/vendor/brotli/c/common/constants.c +7 -7
  13. data/vendor/brotli/c/common/constants.h +2 -5
  14. data/vendor/brotli/c/common/context.c +2 -2
  15. data/vendor/brotli/c/common/context.h +1 -2
  16. data/vendor/brotli/c/common/dictionary.c +4 -5856
  17. data/vendor/brotli/c/common/dictionary.h +1 -2
  18. data/vendor/brotli/c/common/dictionary_inc.h +5847 -0
  19. data/vendor/brotli/c/common/platform.c +0 -4
  20. data/vendor/brotli/c/common/platform.h +182 -43
  21. data/vendor/brotli/c/common/shared_dictionary.c +3 -7
  22. data/vendor/brotli/c/common/shared_dictionary_internal.h +1 -1
  23. data/vendor/brotli/c/common/static_init.h +56 -0
  24. data/vendor/brotli/c/common/transform.c +6 -4
  25. data/vendor/brotli/c/common/transform.h +1 -2
  26. data/vendor/brotli/c/common/version.h +3 -3
  27. data/vendor/brotli/c/dec/bit_reader.c +2 -3
  28. data/vendor/brotli/c/dec/bit_reader.h +0 -4
  29. data/vendor/brotli/c/dec/decode.c +128 -39
  30. data/vendor/brotli/c/dec/huffman.c +2 -5
  31. data/vendor/brotli/c/dec/huffman.h +0 -2
  32. data/vendor/brotli/c/dec/prefix.c +67 -0
  33. data/vendor/brotli/c/dec/prefix.h +18 -708
  34. data/vendor/brotli/c/dec/prefix_inc.h +707 -0
  35. data/vendor/brotli/c/dec/state.c +18 -15
  36. data/vendor/brotli/c/dec/state.h +2 -6
  37. data/vendor/brotli/c/dec/static_init.c +53 -0
  38. data/vendor/brotli/c/dec/static_init.h +30 -0
  39. data/vendor/brotli/c/enc/backward_references.c +32 -8
  40. data/vendor/brotli/c/enc/backward_references.h +1 -5
  41. data/vendor/brotli/c/enc/backward_references_hq.c +15 -15
  42. data/vendor/brotli/c/enc/backward_references_hq.h +1 -5
  43. data/vendor/brotli/c/enc/bit_cost.c +28 -4
  44. data/vendor/brotli/c/enc/bit_cost.h +8 -40
  45. data/vendor/brotli/c/enc/bit_cost_inc.h +1 -1
  46. data/vendor/brotli/c/enc/block_splitter.c +9 -12
  47. data/vendor/brotli/c/enc/block_splitter.h +0 -3
  48. data/vendor/brotli/c/enc/block_splitter_inc.h +14 -8
  49. data/vendor/brotli/c/enc/brotli_bit_stream.c +10 -9
  50. data/vendor/brotli/c/enc/brotli_bit_stream.h +0 -6
  51. data/vendor/brotli/c/enc/cluster.c +0 -2
  52. data/vendor/brotli/c/enc/cluster.h +0 -2
  53. data/vendor/brotli/c/enc/command.c +1 -1
  54. data/vendor/brotli/c/enc/command.h +8 -10
  55. data/vendor/brotli/c/enc/compound_dictionary.c +3 -5
  56. data/vendor/brotli/c/enc/compound_dictionary.h +1 -4
  57. data/vendor/brotli/c/enc/compress_fragment.c +3 -13
  58. data/vendor/brotli/c/enc/compress_fragment.h +0 -2
  59. data/vendor/brotli/c/enc/compress_fragment_two_pass.c +5 -15
  60. data/vendor/brotli/c/enc/compress_fragment_two_pass.h +0 -2
  61. data/vendor/brotli/c/enc/dictionary_hash.c +127 -1830
  62. data/vendor/brotli/c/enc/dictionary_hash.h +23 -3
  63. data/vendor/brotli/c/enc/dictionary_hash_inc.h +1829 -0
  64. data/vendor/brotli/c/enc/encode.c +77 -52
  65. data/vendor/brotli/c/enc/encoder_dict.c +9 -7
  66. data/vendor/brotli/c/enc/encoder_dict.h +2 -4
  67. data/vendor/brotli/c/enc/entropy_encode.c +3 -6
  68. data/vendor/brotli/c/enc/entropy_encode.h +2 -4
  69. data/vendor/brotli/c/enc/entropy_encode_static.h +18 -12
  70. data/vendor/brotli/c/enc/fast_log.c +1 -1
  71. data/vendor/brotli/c/enc/fast_log.h +2 -3
  72. data/vendor/brotli/c/enc/find_match_length.h +0 -2
  73. data/vendor/brotli/c/enc/hash.h +38 -31
  74. data/vendor/brotli/c/enc/hash_base.h +38 -0
  75. data/vendor/brotli/c/enc/hash_forgetful_chain_inc.h +11 -1
  76. data/vendor/brotli/c/enc/hash_longest_match64_inc.h +24 -7
  77. data/vendor/brotli/c/enc/hash_longest_match64_simd_inc.h +304 -0
  78. data/vendor/brotli/c/enc/hash_longest_match_inc.h +30 -11
  79. data/vendor/brotli/c/enc/hash_longest_match_quickly_inc.h +4 -0
  80. data/vendor/brotli/c/enc/hash_longest_match_simd_inc.h +278 -0
  81. data/vendor/brotli/c/enc/histogram.c +1 -0
  82. data/vendor/brotli/c/enc/histogram.h +0 -4
  83. data/vendor/brotli/c/enc/literal_cost.c +4 -6
  84. data/vendor/brotli/c/enc/literal_cost.h +0 -2
  85. data/vendor/brotli/c/enc/matching_tag_mask.h +69 -0
  86. data/vendor/brotli/c/enc/memory.c +0 -5
  87. data/vendor/brotli/c/enc/memory.h +0 -4
  88. data/vendor/brotli/c/enc/metablock.c +7 -9
  89. data/vendor/brotli/c/enc/metablock.h +3 -3
  90. data/vendor/brotli/c/enc/metablock_inc.h +4 -4
  91. data/vendor/brotli/c/enc/params.h +0 -1
  92. data/vendor/brotli/c/enc/prefix.h +0 -2
  93. data/vendor/brotli/c/enc/quality.h +17 -10
  94. data/vendor/brotli/c/enc/ringbuffer.h +1 -4
  95. data/vendor/brotli/c/enc/state.h +2 -2
  96. data/vendor/brotli/c/enc/static_dict.c +5 -11
  97. data/vendor/brotli/c/enc/static_dict.h +1 -3
  98. data/vendor/brotli/c/enc/static_dict_lut.c +224 -0
  99. data/vendor/brotli/c/enc/static_dict_lut.h +20 -5837
  100. data/vendor/brotli/c/enc/static_dict_lut_inc.h +5830 -0
  101. data/vendor/brotli/c/enc/static_init.c +59 -0
  102. data/vendor/brotli/c/enc/static_init.h +30 -0
  103. data/vendor/brotli/c/enc/utf8_util.c +1 -1
  104. data/vendor/brotli/c/enc/utf8_util.h +0 -2
  105. data/vendor/brotli/c/enc/write_bits.h +0 -2
  106. data/vendor/brotli/c/include/brotli/decode.h +1 -1
  107. data/vendor/brotli/c/include/brotli/encode.h +5 -1
  108. data/vendor/brotli/c/include/brotli/port.h +4 -7
  109. data/vendor/brotli/c/include/brotli/types.h +2 -2
  110. metadata +24 -28
  111. data/.github/workflows/main.yml +0 -37
  112. data/.github/workflows/publish.yml +0 -24
  113. data/.gitignore +0 -18
  114. data/.gitmodules +0 -3
  115. data/.rspec +0 -2
  116. data/Dockerfile +0 -38
  117. data/Gemfile +0 -10
  118. data/Rakefile +0 -42
  119. data/bin/before_install.sh +0 -9
  120. data/bin/console +0 -14
  121. data/bin/setup +0 -8
  122. data/brotli.gemspec +0 -27
  123. data/smoke.sh +0 -8
  124. data/test/brotli_test.rb +0 -183
  125. data/test/brotli_writer_test.rb +0 -93
  126. data/test/test_helper.rb +0 -9
  127. data/vendor/brotli/c/common/dictionary.bin +0 -432
  128. data/vendor/brotli/c/common/dictionary.bin.br +0 -0
data/ext/brotli/brotli.c CHANGED
@@ -1,21 +1,65 @@
1
1
  #include "brotli.h"
2
2
 
3
- #define CSTR2SYM(x) ID2SYM(rb_intern(x))
4
-
5
3
  static VALUE rb_eBrotli;
4
+ static ID id_cmp;
5
+ static ID id_dictionary;
6
+ static ID id_font;
7
+ static ID id_generic;
8
+ static ID id_lgblock;
9
+ static ID id_lgwin;
10
+ static ID id_mode;
11
+ static ID id_output_buffer_limit;
12
+ static ID id_quality;
13
+ static ID id_read;
14
+ static ID id_text;
6
15
 
7
16
  static inline void*
8
17
  brotli_alloc(void* opaque, size_t size)
9
18
  {
19
+ (void)opaque;
10
20
  return ruby_xmalloc(size);
11
21
  }
12
22
 
13
23
  static inline void
14
24
  brotli_free(void* opaque, void* address)
15
25
  {
16
- if (address) {
17
- ruby_xfree(address);
26
+ (void)opaque;
27
+ ruby_xfree(address);
28
+ }
29
+
30
+ static VALUE
31
+ brotli_hash_lookup(VALUE hash, ID key)
32
+ {
33
+ if (NIL_P(hash)) {
34
+ return Qnil;
18
35
  }
36
+
37
+ Check_Type(hash, T_HASH);
38
+ return rb_hash_lookup(hash, ID2SYM(key));
39
+ }
40
+
41
+ static BrotliEncoderState*
42
+ brotli_encoder_state_create(void)
43
+ {
44
+ BrotliEncoderState* state = BrotliEncoderCreateInstance(brotli_alloc, brotli_free, NULL);
45
+
46
+ if (!state) {
47
+ rb_raise(rb_eNoMemError, "BrotliEncoderCreateInstance failed");
48
+ }
49
+
50
+ return state;
51
+ }
52
+
53
+ static BrotliDecoderState*
54
+ brotli_decoder_state_create(void)
55
+ {
56
+ BrotliDecoderState* state = BrotliDecoderCreateInstance(brotli_alloc, brotli_free, NULL);
57
+
58
+ if (!state) {
59
+ rb_raise(rb_eNoMemError, "BrotliDecoderCreateInstance failed");
60
+ }
61
+
62
+ return state;
19
63
  }
20
64
 
21
65
  /*******************************************************************************
@@ -32,6 +76,17 @@ typedef struct {
32
76
  size_t dict_len;
33
77
  } brotli_inflate_args_t;
34
78
 
79
+ static void
80
+ brotli_inflate_cleanup(brotli_inflate_args_t* args)
81
+ {
82
+ delete_buffer(args->buffer);
83
+ args->buffer = NULL;
84
+ if (args->s) {
85
+ BrotliDecoderDestroyInstance(args->s);
86
+ args->s = NULL;
87
+ }
88
+ }
89
+
35
90
  static void*
36
91
  brotli_inflate_no_gvl(void *arg)
37
92
  {
@@ -46,13 +101,11 @@ brotli_inflate_no_gvl(void *arg)
46
101
  buffer_t* buffer = args->buffer;
47
102
  BrotliDecoderState* s = args->s;
48
103
 
49
- #ifdef HAVE_BROTLIDECODERATTACHDICTIONARY
50
104
  /* Attach dictionary if provided */
51
105
  if (args->dict && args->dict_len > 0) {
52
106
  BrotliDecoderAttachDictionary(s, BROTLI_SHARED_DICTIONARY_RAW,
53
107
  args->dict_len, args->dict);
54
108
  }
55
- #endif
56
109
 
57
110
  for (;;) {
58
111
  r = BrotliDecoderDecompressStream(s,
@@ -78,73 +131,74 @@ brotli_inflate_no_gvl(void *arg)
78
131
  return arg;
79
132
  }
80
133
 
81
- static ID id_read;
134
+ static VALUE
135
+ brotli_inflate_take_result(VALUE arg)
136
+ {
137
+ brotli_inflate_args_t *args = (brotli_inflate_args_t *)arg;
138
+ return rb_str_new(args->buffer->ptr, args->buffer->used);
139
+ }
82
140
 
83
141
  static VALUE
84
142
  brotli_inflate(int argc, VALUE *argv, VALUE self)
85
143
  {
86
- VALUE str = Qnil, opts = Qnil, value = Qnil, dict = Qnil;
87
- brotli_inflate_args_t args;
144
+ VALUE str = Qnil;
145
+ VALUE opts = Qnil;
146
+ VALUE value = Qnil;
147
+ VALUE dict = Qnil;
148
+ const char *error = NULL;
149
+ brotli_inflate_args_t args = {0};
150
+
151
+ (void)self;
88
152
 
89
153
  rb_scan_args(argc, argv, "11", &str, &opts);
90
154
 
91
155
  if (rb_respond_to(str, id_read)) {
92
- str = rb_funcall(str, id_read, 0, 0);
156
+ str = rb_funcall(str, id_read, 0);
93
157
  }
94
158
 
95
159
  StringValue(str);
96
-
97
- /* Extract dictionary from options if provided */
98
- if (!NIL_P(opts)) {
99
- Check_Type(opts, T_HASH);
100
- dict = rb_hash_aref(opts, CSTR2SYM("dictionary"));
160
+ dict = brotli_hash_lookup(opts, id_dictionary);
161
+ if (!NIL_P(dict)) {
162
+ StringValue(dict);
101
163
  }
102
164
 
103
165
  args.str = (uint8_t*)RSTRING_PTR(str);
104
166
  args.len = (size_t)RSTRING_LEN(str);
167
+ args.s = brotli_decoder_state_create();
105
168
  args.buffer = create_buffer(BUFSIZ);
106
- args.s = BrotliDecoderCreateInstance(brotli_alloc,
107
- brotli_free,
108
- NULL);
109
169
  args.r = BROTLI_DECODER_RESULT_ERROR;
110
170
 
111
- /* Set dictionary parameters */
112
171
  if (!NIL_P(dict)) {
113
- #ifdef HAVE_BROTLIDECODERATTACHDICTIONARY
114
- StringValue(dict);
115
172
  args.dict = (uint8_t*)RSTRING_PTR(dict);
116
173
  args.dict_len = (size_t)RSTRING_LEN(dict);
117
- #else
118
- rb_raise(rb_eBrotli, "Dictionary support not available in this build");
119
- #endif
120
174
  } else {
121
175
  args.dict = NULL;
122
176
  args.dict_len = 0;
123
177
  }
124
178
 
125
- #ifdef HAVE_RUBY_THREAD_H
126
- rb_thread_call_without_gvl(brotli_inflate_no_gvl, (void *)&args, NULL, NULL);
127
- #else
128
- brotli_inflate_no_gvl((void *)&args);
129
- #endif
179
+ rb_thread_call_without_gvl(brotli_inflate_no_gvl, &args, NULL, NULL);
180
+ RB_GC_GUARD(str);
181
+ RB_GC_GUARD(dict);
182
+
130
183
  if (args.r == BROTLI_DECODER_RESULT_SUCCESS) {
131
- value = rb_str_new(args.buffer->ptr, args.buffer->used);
132
- delete_buffer(args.buffer);
133
- BrotliDecoderDestroyInstance(args.s);
184
+ int state = 0;
185
+ value = rb_protect(brotli_inflate_take_result, (VALUE)&args, &state);
186
+ if (state) {
187
+ brotli_inflate_cleanup(&args);
188
+ rb_jump_tag(state);
189
+ }
134
190
  } else if (args.r == BROTLI_DECODER_RESULT_ERROR) {
135
- const char * error = BrotliDecoderErrorString(BrotliDecoderGetErrorCode(args.s));
136
- delete_buffer(args.buffer);
137
- BrotliDecoderDestroyInstance(args.s);
138
- rb_raise(rb_eBrotli, "%s", error);
191
+ error = BrotliDecoderErrorString(BrotliDecoderGetErrorCode(args.s));
139
192
  } else if (args.r == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT) {
140
- delete_buffer(args.buffer);
141
- BrotliDecoderDestroyInstance(args.s);
142
- rb_raise(rb_eBrotli, "Needs more input");
143
- } else if (args.r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) {
144
- /* never reach to this block */
145
- delete_buffer(args.buffer);
146
- BrotliDecoderDestroyInstance(args.s);
147
- rb_raise(rb_eBrotli, "Needs more output");
193
+ error = "Needs more input";
194
+ } else {
195
+ error = "Needs more output";
196
+ }
197
+
198
+ brotli_inflate_cleanup(&args);
199
+
200
+ if (error) {
201
+ rb_raise(rb_eBrotli, "%s", error);
148
202
  }
149
203
 
150
204
  return value;
@@ -154,81 +208,113 @@ brotli_inflate(int argc, VALUE *argv, VALUE self)
154
208
  * deflate
155
209
  ******************************************************************************/
156
210
 
157
- static void
211
+ static const char *
158
212
  brotli_deflate_set_mode(BrotliEncoderState* s, VALUE value)
159
213
  {
160
214
  if (NIL_P(value)) {
161
- return;
215
+ return NULL;
216
+ }
217
+
218
+ if (value == ID2SYM(id_generic)) {
219
+ BrotliEncoderSetParameter(s, BROTLI_PARAM_MODE, BROTLI_MODE_GENERIC);
220
+ } else if (value == ID2SYM(id_text)) {
221
+ BrotliEncoderSetParameter(s, BROTLI_PARAM_MODE, BROTLI_MODE_TEXT);
222
+ } else if (value == ID2SYM(id_font)) {
223
+ BrotliEncoderSetParameter(s, BROTLI_PARAM_MODE, BROTLI_MODE_FONT);
162
224
  } else {
163
- if (value == CSTR2SYM("generic")) {
164
- BrotliEncoderSetParameter(s, BROTLI_PARAM_MODE, BROTLI_MODE_GENERIC);
165
- } else if (value == CSTR2SYM("text")) {
166
- BrotliEncoderSetParameter(s, BROTLI_PARAM_MODE, BROTLI_MODE_TEXT);
167
- } else if (value == CSTR2SYM("font")) {
168
- BrotliEncoderSetParameter(s, BROTLI_PARAM_MODE, BROTLI_MODE_FONT);
169
- } else {
170
- rb_raise(rb_eArgError, "invalid mode");
171
- }
225
+ return "invalid mode";
172
226
  }
227
+
228
+ return NULL;
173
229
  }
174
230
 
175
- static void
231
+ static const char *
176
232
  brotli_deflate_set_quality(BrotliEncoderState* s, VALUE value)
177
233
  {
234
+ long param;
235
+
178
236
  if (NIL_P(value)) {
179
- return;
237
+ return NULL;
238
+ }
239
+
240
+ if (!FIXNUM_P(value)) {
241
+ return "invalid quality value. Should be 0 to 11.";
242
+ }
243
+
244
+ param = FIX2LONG(value);
245
+ if (0 <= param && param <= 11) {
246
+ BrotliEncoderSetParameter(s, BROTLI_PARAM_QUALITY, (uint32_t)param);
180
247
  } else {
181
- int32_t param = NUM2INT(value);
182
- if (0 <= param && param <= 11) {
183
- BrotliEncoderSetParameter(s, BROTLI_PARAM_QUALITY, param);
184
- } else {
185
- rb_raise(rb_eArgError, "invalid quality value. Should be 0 to 11.");
186
- }
248
+ return "invalid quality value. Should be 0 to 11.";
187
249
  }
250
+
251
+ return NULL;
188
252
  }
189
253
 
190
- static void
254
+ static const char *
191
255
  brotli_deflate_set_lgwin(BrotliEncoderState* s, VALUE value)
192
256
  {
257
+ long param;
258
+
193
259
  if (NIL_P(value)) {
194
- return;
260
+ return NULL;
261
+ }
262
+
263
+ if (!FIXNUM_P(value)) {
264
+ return "invalid lgwin value. Should be 10 to 24.";
265
+ }
266
+
267
+ param = FIX2LONG(value);
268
+ if (10 <= param && param <= 24) {
269
+ BrotliEncoderSetParameter(s, BROTLI_PARAM_LGWIN, (uint32_t)param);
195
270
  } else {
196
- int32_t param = NUM2INT(value);
197
- if (10 <= param && param <= 24) {
198
- BrotliEncoderSetParameter(s, BROTLI_PARAM_LGWIN, param);
199
- } else {
200
- rb_raise(rb_eArgError, "invalid lgwin value. Should be 10 to 24.");
201
- }
271
+ return "invalid lgwin value. Should be 10 to 24.";
202
272
  }
273
+
274
+ return NULL;
203
275
  }
204
276
 
205
- static void
277
+ static const char *
206
278
  brotli_deflate_set_lgblock(BrotliEncoderState* s, VALUE value)
207
279
  {
280
+ long param;
281
+
208
282
  if (NIL_P(value)) {
209
- return;
283
+ return NULL;
284
+ }
285
+
286
+ if (!FIXNUM_P(value)) {
287
+ return "invalid lgblock value. Should be 0 or 16 to 24.";
288
+ }
289
+
290
+ param = FIX2LONG(value);
291
+ if (param == 0 || (16 <= param && param <= 24)) {
292
+ BrotliEncoderSetParameter(s, BROTLI_PARAM_LGBLOCK, (uint32_t)param);
210
293
  } else {
211
- int32_t param = NUM2INT(value);
212
- if ((param == 0) || (16 <= param && param <= 24)) {
213
- BrotliEncoderSetParameter(s, BROTLI_PARAM_LGBLOCK, param);
214
- } else {
215
- rb_raise(rb_eArgError, "invalid lgblock value. Should be 0 or 16 to 24.");
216
- }
294
+ return "invalid lgblock value. Should be 0 or 16 to 24.";
217
295
  }
296
+
297
+ return NULL;
218
298
  }
219
299
 
220
- static BrotliEncoderState*
300
+ static const char *
221
301
  brotli_deflate_parse_options(BrotliEncoderState* s, VALUE opts)
222
302
  {
223
- if (!NIL_P(opts)) {
224
- Check_Type(opts, T_HASH);
225
- brotli_deflate_set_mode(s, rb_hash_aref(opts, CSTR2SYM("mode")));
226
- brotli_deflate_set_quality(s, rb_hash_aref(opts, CSTR2SYM("quality")));
227
- brotli_deflate_set_lgwin(s, rb_hash_aref(opts, CSTR2SYM("lgwin")));
228
- brotli_deflate_set_lgblock(s, rb_hash_aref(opts, CSTR2SYM("lgblock")));
229
- }
303
+ const char *err;
230
304
 
231
- return s;
305
+ err = brotli_deflate_set_mode(s, brotli_hash_lookup(opts, id_mode));
306
+ if (err) {
307
+ return err;
308
+ }
309
+ err = brotli_deflate_set_quality(s, brotli_hash_lookup(opts, id_quality));
310
+ if (err) {
311
+ return err;
312
+ }
313
+ err = brotli_deflate_set_lgwin(s, brotli_hash_lookup(opts, id_lgwin));
314
+ if (err) {
315
+ return err;
316
+ }
317
+ return brotli_deflate_set_lgblock(s, brotli_hash_lookup(opts, id_lgblock));
232
318
  }
233
319
 
234
320
  typedef struct {
@@ -237,10 +323,24 @@ typedef struct {
237
323
  BrotliEncoderState* s;
238
324
  buffer_t* buffer;
239
325
  BROTLI_BOOL finished;
240
- uint8_t *dict;
241
- size_t dict_len;
326
+ BrotliEncoderPreparedDictionary* prepared_dict;
242
327
  } brotli_deflate_args_t;
243
328
 
329
+ static void
330
+ brotli_deflate_cleanup(brotli_deflate_args_t* args)
331
+ {
332
+ delete_buffer(args->buffer);
333
+ args->buffer = NULL;
334
+ if (args->s) {
335
+ BrotliEncoderDestroyInstance(args->s);
336
+ args->s = NULL;
337
+ }
338
+ if (args->prepared_dict) {
339
+ BrotliEncoderDestroyPreparedDictionary(args->prepared_dict);
340
+ args->prepared_dict = NULL;
341
+ }
342
+ }
343
+
244
344
  static void*
245
345
  brotli_deflate_no_gvl(void *arg)
246
346
  {
@@ -255,19 +355,6 @@ brotli_deflate_no_gvl(void *arg)
255
355
  buffer_t* buffer = args->buffer;
256
356
  BrotliEncoderState* s = args->s;
257
357
 
258
- #if defined(HAVE_BROTLIENCODERPREPAREDICTIONARY) && defined(HAVE_BROTLIENCODERATTACHPREPAREDDICTIONARY)
259
- /* Attach dictionary if provided */
260
- if (args->dict && args->dict_len > 0) {
261
- BrotliEncoderPreparedDictionary* dict = BrotliEncoderPrepareDictionary(
262
- BROTLI_SHARED_DICTIONARY_RAW, args->dict_len, args->dict,
263
- BROTLI_MAX_QUALITY, brotli_alloc, brotli_free, NULL);
264
- if (dict) {
265
- BrotliEncoderAttachPreparedDictionary(s, dict);
266
- /* Note: dict is owned by encoder after attach, no need to free */
267
- }
268
- }
269
- #endif
270
-
271
358
  for (;;) {
272
359
  r = BrotliEncoderCompressStream(s,
273
360
  BROTLI_OPERATION_FINISH,
@@ -291,59 +378,85 @@ brotli_deflate_no_gvl(void *arg)
291
378
  return arg;
292
379
  }
293
380
 
381
+ static VALUE
382
+ brotli_deflate_take_result(VALUE arg)
383
+ {
384
+ brotli_deflate_args_t *args = (brotli_deflate_args_t *)arg;
385
+ return rb_str_new(args->buffer->ptr, args->buffer->used);
386
+ }
387
+
294
388
  static VALUE
295
389
  brotli_deflate(int argc, VALUE *argv, VALUE self)
296
390
  {
297
- VALUE str = Qnil, opts = Qnil, value = Qnil, dict = Qnil;
298
- brotli_deflate_args_t args;
391
+ VALUE str = Qnil;
392
+ VALUE opts = Qnil;
393
+ VALUE value = Qnil;
394
+ VALUE dict = Qnil;
395
+ brotli_deflate_args_t args = {0};
396
+ size_t max_compressed_size;
397
+ const char *err;
398
+
399
+ (void)self;
299
400
 
300
401
  rb_scan_args(argc, argv, "11", &str, &opts);
301
402
  if (NIL_P(str)) {
302
403
  rb_raise(rb_eArgError, "input should not be nil");
303
- return Qnil;
304
404
  }
305
405
  StringValue(str);
306
-
307
- /* Extract dictionary from options if provided */
308
- if (!NIL_P(opts)) {
309
- Check_Type(opts, T_HASH);
310
- dict = rb_hash_aref(opts, CSTR2SYM("dictionary"));
406
+ dict = brotli_hash_lookup(opts, id_dictionary);
407
+ if (!NIL_P(dict)) {
408
+ StringValue(dict);
311
409
  }
312
410
 
313
411
  args.str = (uint8_t*)RSTRING_PTR(str);
314
412
  args.len = (size_t)RSTRING_LEN(str);
315
- args.s = brotli_deflate_parse_options(
316
- BrotliEncoderCreateInstance(brotli_alloc, brotli_free, NULL),
317
- opts);
318
- size_t max_compressed_size = BrotliEncoderMaxCompressedSize(args.len);
413
+ args.s = brotli_encoder_state_create();
414
+ err = brotli_deflate_parse_options(args.s, opts);
415
+ if (err) {
416
+ brotli_deflate_cleanup(&args);
417
+ rb_raise(rb_eArgError, "%s", err);
418
+ }
419
+ max_compressed_size = BrotliEncoderMaxCompressedSize(args.len);
319
420
  args.buffer = create_buffer(max_compressed_size);
320
421
  args.finished = BROTLI_FALSE;
321
422
 
322
- /* Set dictionary parameters */
423
+ args.prepared_dict = NULL;
323
424
  if (!NIL_P(dict)) {
324
- #if defined(HAVE_BROTLIENCODERPREPAREDICTIONARY) && defined(HAVE_BROTLIENCODERATTACHPREPAREDDICTIONARY)
325
- StringValue(dict);
326
- args.dict = (uint8_t*)RSTRING_PTR(dict);
327
- args.dict_len = (size_t)RSTRING_LEN(dict);
328
- #else
329
- rb_raise(rb_eBrotli, "Dictionary support not available in this build");
330
- #endif
331
- } else {
332
- args.dict = NULL;
333
- args.dict_len = 0;
425
+ args.prepared_dict = BrotliEncoderPrepareDictionary(
426
+ BROTLI_SHARED_DICTIONARY_RAW,
427
+ (size_t)RSTRING_LEN(dict),
428
+ (const uint8_t*)RSTRING_PTR(dict),
429
+ BROTLI_MAX_QUALITY,
430
+ brotli_alloc,
431
+ brotli_free,
432
+ NULL);
433
+ if (!args.prepared_dict) {
434
+ brotli_deflate_cleanup(&args);
435
+ rb_raise(rb_eBrotli, "Failed to prepare dictionary for compression");
436
+ }
437
+ if (!BrotliEncoderAttachPreparedDictionary(args.s, args.prepared_dict)) {
438
+ brotli_deflate_cleanup(&args);
439
+ rb_raise(rb_eBrotli, "Failed to attach dictionary for compression");
440
+ }
334
441
  }
335
442
 
336
- #ifdef HAVE_RUBY_THREAD_H
337
- rb_thread_call_without_gvl(brotli_deflate_no_gvl, (void *)&args, NULL, NULL);
338
- #else
339
- brotli_deflate_no_gvl((void *)&args);
340
- #endif
443
+ rb_thread_call_without_gvl(brotli_deflate_no_gvl, &args, NULL, NULL);
444
+ RB_GC_GUARD(str);
445
+ RB_GC_GUARD(dict);
341
446
  if (args.finished == BROTLI_TRUE) {
342
- value = rb_str_new(args.buffer->ptr, args.buffer->used);
447
+ int state = 0;
448
+ value = rb_protect(brotli_deflate_take_result, (VALUE)&args, &state);
449
+ if (state) {
450
+ brotli_deflate_cleanup(&args);
451
+ rb_jump_tag(state);
452
+ }
343
453
  }
344
454
 
345
- delete_buffer(args.buffer);
346
- BrotliEncoderDestroyInstance(args.s);
455
+ brotli_deflate_cleanup(&args);
456
+
457
+ if (args.finished != BROTLI_TRUE) {
458
+ rb_raise(rb_eBrotli, "Failed to compress");
459
+ }
347
460
 
348
461
  return value;
349
462
  }
@@ -355,212 +468,706 @@ brotli_deflate(int argc, VALUE *argv, VALUE self)
355
468
  static VALUE brotli_version(VALUE klass) {
356
469
  uint32_t ver = BrotliEncoderVersion();
357
470
  char version[255];
471
+
472
+ (void)klass;
358
473
  snprintf(version, sizeof(version), "%u.%u.%u", ver >> 24, (ver >> 12) & 0xFFF, ver & 0xFFF);
359
- return rb_str_new2(version);
474
+ return rb_str_new_cstr(version);
360
475
  }
361
476
 
362
477
  /*******************************************************************************
363
- * Writer
478
+ * Streaming APIs
364
479
  ******************************************************************************/
365
480
 
366
- static ID id_write, id_flush, id_close;
367
-
368
- struct brotli {
369
- VALUE io;
370
- BrotliEncoderState* state;
371
- };
481
+ static VALUE rb_cBrotliCompressor;
482
+ static VALUE rb_cBrotliDecompressor;
372
483
 
373
- static void br_mark(void *p)
484
+ static uint8_t*
485
+ brotli_copy_string_data(VALUE string, size_t* len)
374
486
  {
375
- struct brotli *br = p;
376
- rb_gc_mark(br->io);
487
+ size_t copy_len;
488
+ size_t alloc_len;
489
+ uint8_t* copy;
490
+
491
+ StringValue(string);
492
+ copy_len = (size_t)RSTRING_LEN(string);
493
+ alloc_len = copy_len > 0 ? copy_len : 1;
494
+ copy = brotli_alloc(NULL, alloc_len);
495
+ if (copy_len > 0) {
496
+ memcpy(copy, RSTRING_PTR(string), copy_len);
497
+ }
498
+ *len = copy_len;
499
+ return copy;
377
500
  }
378
501
 
379
- static void br_free(void *p)
380
- {
381
- struct brotli* br = p;
382
- BrotliEncoderDestroyInstance(br->state);
383
- br->state = NULL;
384
- br->io = Qnil;
385
- ruby_xfree(br);
386
- }
502
+ typedef struct {
503
+ BrotliEncoderState* state;
504
+ BrotliEncoderPreparedDictionary* prepared_dict;
505
+ uint8_t* dict_data;
506
+ size_t dict_len;
507
+ BROTLI_BOOL finished;
508
+ } brotli_encoder_t;
387
509
 
388
- static size_t br_memsize(const void *p)
389
- {
390
- return sizeof(struct brotli);
391
- }
510
+ typedef struct {
511
+ brotli_encoder_t encoder;
512
+ } brotli_compressor_t;
392
513
 
393
- static const rb_data_type_t brotli_data_type = {
394
- "brotli",
395
- { br_mark, br_free, br_memsize },
396
- 0, 0, RUBY_TYPED_FREE_IMMEDIATELY
397
- };
514
+ typedef struct {
515
+ BrotliDecoderState* state;
516
+ uint8_t* dict_data;
517
+ size_t dict_len;
518
+ VALUE pending_input;
519
+ BROTLI_BOOL needs_more_output;
520
+ BROTLI_BOOL finished;
521
+ } brotli_decompressor_t;
398
522
 
399
523
  typedef struct {
400
524
  BrotliEncoderState* state;
401
525
  BrotliEncoderOperation op;
402
526
  size_t available_in;
403
527
  const uint8_t* next_in;
528
+ BROTLI_BOOL ok;
404
529
  } brotli_encoder_args_t;
405
530
 
531
+ typedef struct {
532
+ BrotliDecoderState* state;
533
+ size_t available_in;
534
+ const uint8_t* next_in;
535
+ size_t available_out;
536
+ uint8_t* next_out;
537
+ BrotliDecoderResult result;
538
+ } brotli_decoder_args_t;
539
+
406
540
  static void* compress_no_gvl(void *ptr) {
407
541
  brotli_encoder_args_t *args = ptr;
408
542
  size_t zero = 0;
409
- if (!BrotliEncoderCompressStream(args->state, args->op,
410
- &args->available_in, &args->next_in,
411
- &zero, NULL, NULL)) {
412
- rb_raise(rb_eBrotli, "BrotliEncoderCompressStream failed");
413
- }
543
+ args->ok = BrotliEncoderCompressStream(args->state, args->op,
544
+ &args->available_in, &args->next_in,
545
+ &zero, NULL, NULL);
414
546
  return NULL;
415
547
  }
416
548
 
417
- static size_t push_output(struct brotli *br) {
418
- size_t len = 0;
419
- if (BrotliEncoderHasMoreOutput(br->state)) {
420
- const uint8_t* out = BrotliEncoderTakeOutput(br->state, &len);
549
+ static void* decompress_no_gvl(void *ptr) {
550
+ brotli_decoder_args_t *args = ptr;
551
+ args->result = BrotliDecoderDecompressStream(args->state,
552
+ &args->available_in,
553
+ &args->next_in,
554
+ &args->available_out,
555
+ &args->next_out,
556
+ NULL);
557
+ return NULL;
558
+ }
559
+
560
+ static void
561
+ brotli_encoder_step(brotli_encoder_args_t *args)
562
+ {
563
+ rb_thread_call_without_gvl(compress_no_gvl, (void*)args, NULL, NULL);
564
+ }
565
+
566
+ static void
567
+ brotli_decompressor_step(brotli_decoder_args_t *args)
568
+ {
569
+ rb_thread_call_without_gvl(decompress_no_gvl, (void*)args, NULL, NULL);
570
+ }
571
+
572
+ static void
573
+ brotli_encoder_destroy(brotli_encoder_t* encoder)
574
+ {
575
+ if (encoder->state) {
576
+ BrotliEncoderDestroyInstance(encoder->state);
577
+ encoder->state = NULL;
578
+ }
579
+ if (encoder->prepared_dict) {
580
+ BrotliEncoderDestroyPreparedDictionary(encoder->prepared_dict);
581
+ encoder->prepared_dict = NULL;
582
+ }
583
+ brotli_free(NULL, encoder->dict_data);
584
+ encoder->dict_data = NULL;
585
+ encoder->dict_len = 0;
586
+ encoder->finished = BROTLI_FALSE;
587
+ }
588
+
589
+ static void
590
+ brotli_encoder_reset(brotli_encoder_t* encoder)
591
+ {
592
+ brotli_encoder_destroy(encoder);
593
+ encoder->state = brotli_encoder_state_create();
594
+ }
595
+
596
+ static void
597
+ brotli_encoder_take_output_to_string(BrotliEncoderState* state, VALUE output)
598
+ {
599
+ while (BrotliEncoderHasMoreOutput(state)) {
600
+ size_t len = 0;
601
+ const uint8_t* out = BrotliEncoderTakeOutput(state, &len);
421
602
  if (len > 0) {
422
- rb_funcall(br->io, id_write, 1, rb_str_new((const char*)out, len));
603
+ rb_str_cat(output, (const char*)out, len);
423
604
  }
424
605
  }
425
- return len;
426
606
  }
427
607
 
428
- static VALUE rb_writer_alloc(VALUE klass) {
429
- struct brotli *br;
430
- VALUE obj = TypedData_Make_Struct(klass, struct brotli, &brotli_data_type, br);
431
- br->io = Qnil;
432
- br->state = BrotliEncoderCreateInstance(brotli_alloc, brotli_free, NULL);
433
- if (!br->state) {
434
- rb_raise(rb_eNoMemError, "BrotliEncoderCreateInstance failed");
435
- return Qnil;
608
+ static void
609
+ brotli_encoder_attach_dictionary(brotli_encoder_t* encoder, VALUE opts)
610
+ {
611
+ VALUE dict = brotli_hash_lookup(opts, id_dictionary);
612
+
613
+ if (NIL_P(dict)) {
614
+ return;
615
+ }
616
+
617
+ encoder->dict_data = brotli_copy_string_data(dict, &encoder->dict_len);
618
+ encoder->prepared_dict = BrotliEncoderPrepareDictionary(
619
+ BROTLI_SHARED_DICTIONARY_RAW,
620
+ encoder->dict_len,
621
+ encoder->dict_data,
622
+ BROTLI_MAX_QUALITY,
623
+ brotli_alloc,
624
+ brotli_free,
625
+ NULL);
626
+
627
+ if (!encoder->prepared_dict) {
628
+ brotli_free(NULL, encoder->dict_data);
629
+ encoder->dict_data = NULL;
630
+ encoder->dict_len = 0;
631
+ rb_raise(rb_eBrotli, "Failed to prepare dictionary for compression");
632
+ }
633
+
634
+ if (!BrotliEncoderAttachPreparedDictionary(encoder->state, encoder->prepared_dict)) {
635
+ BrotliEncoderDestroyPreparedDictionary(encoder->prepared_dict);
636
+ encoder->prepared_dict = NULL;
637
+ brotli_free(NULL, encoder->dict_data);
638
+ encoder->dict_data = NULL;
639
+ encoder->dict_len = 0;
640
+ rb_raise(rb_eBrotli, "Failed to attach dictionary for compression");
641
+ }
642
+ }
643
+
644
+ static VALUE
645
+ brotli_encoder_stream_to_string(brotli_encoder_t* encoder,
646
+ BrotliEncoderOperation op,
647
+ const uint8_t* input,
648
+ size_t input_len)
649
+ {
650
+ VALUE output = rb_str_buf_new(BUFSIZ);
651
+ brotli_encoder_args_t args = {
652
+ .state = encoder->state,
653
+ .op = op,
654
+ .available_in = input_len,
655
+ .next_in = input,
656
+ .ok = BROTLI_FALSE
657
+ };
658
+
659
+ if (op == BROTLI_OPERATION_PROCESS && input_len == 0) {
660
+ return output;
436
661
  }
662
+
663
+ for (;;) {
664
+ long output_len_before;
665
+ size_t produced;
666
+
667
+ brotli_encoder_step(&args);
668
+ if (args.ok == BROTLI_FALSE) {
669
+ rb_raise(rb_eBrotli, "BrotliEncoderCompressStream failed");
670
+ }
671
+
672
+ output_len_before = RSTRING_LEN(output);
673
+ brotli_encoder_take_output_to_string(encoder->state, output);
674
+ produced = (size_t)(RSTRING_LEN(output) - output_len_before);
675
+
676
+ if (args.available_in > 0 || BrotliEncoderHasMoreOutput(encoder->state)) {
677
+ continue;
678
+ }
679
+ if (op != BROTLI_OPERATION_PROCESS && produced > 0) {
680
+ continue;
681
+ }
682
+ break;
683
+ }
684
+
685
+ return output;
686
+ }
687
+
688
+ /*******************************************************************************
689
+ * Compressor
690
+ ******************************************************************************/
691
+
692
+ static void
693
+ brotli_compressor_free(void *p)
694
+ {
695
+ brotli_compressor_t* br = p;
696
+ brotli_encoder_destroy(&br->encoder);
697
+ ruby_xfree(br);
698
+ }
699
+
700
+ static size_t
701
+ brotli_compressor_memsize(const void *p)
702
+ {
703
+ const brotli_compressor_t *br = p;
704
+ return sizeof(*br) + br->encoder.dict_len;
705
+ }
706
+
707
+ static const rb_data_type_t brotli_compressor_data_type = {
708
+ "brotli_compressor",
709
+ { 0, brotli_compressor_free, brotli_compressor_memsize },
710
+ 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
711
+ };
712
+
713
+ static VALUE
714
+ rb_compressor_alloc(VALUE klass)
715
+ {
716
+ brotli_compressor_t *br;
717
+ VALUE obj = TypedData_Make_Struct(klass, brotli_compressor_t, &brotli_compressor_data_type, br);
718
+ br->encoder.state = NULL;
719
+ br->encoder.prepared_dict = NULL;
720
+ br->encoder.dict_data = NULL;
721
+ br->encoder.dict_len = 0;
722
+ br->encoder.finished = BROTLI_FALSE;
437
723
  return obj;
438
724
  }
439
725
 
440
- static VALUE rb_writer_initialize(int argc, VALUE* argv, VALUE self) {
441
- VALUE io = Qnil;
726
+ static void
727
+ brotli_compressor_ensure_open(brotli_compressor_t* br)
728
+ {
729
+ if (!br->encoder.state) {
730
+ rb_raise(rb_eBrotli, "Compressor is closed");
731
+ }
732
+ }
733
+
734
+ static VALUE
735
+ rb_compressor_initialize(int argc, VALUE* argv, VALUE self)
736
+ {
442
737
  VALUE opts = Qnil;
443
- VALUE dict = Qnil;
444
- rb_scan_args(argc, argv, "11", &io, &opts);
445
- if (NIL_P(io)) {
446
- rb_raise(rb_eArgError, "io should not be nil");
447
- return Qnil;
738
+ brotli_compressor_t *br;
739
+ const char *err;
740
+
741
+ rb_scan_args(argc, argv, "01", &opts);
742
+
743
+ TypedData_Get_Struct(self, brotli_compressor_t, &brotli_compressor_data_type, br);
744
+ brotli_encoder_reset(&br->encoder);
745
+ err = brotli_deflate_parse_options(br->encoder.state, opts);
746
+ if (err) {
747
+ rb_raise(rb_eArgError, "%s", err);
448
748
  }
749
+ brotli_encoder_attach_dictionary(&br->encoder, opts);
449
750
 
450
- struct brotli *br;
451
- TypedData_Get_Struct(self, struct brotli, &brotli_data_type, br);
452
- brotli_deflate_parse_options(br->state, opts);
453
- br->io = io;
454
-
455
- #if defined(HAVE_BROTLIENCODERPREPAREDICTIONARY) && defined(HAVE_BROTLIENCODERATTACHPREPAREDDICTIONARY)
456
- /* Extract and attach dictionary if provided */
457
- if (!NIL_P(opts)) {
458
- Check_Type(opts, T_HASH);
459
- dict = rb_hash_aref(opts, CSTR2SYM("dictionary"));
460
- if (!NIL_P(dict)) {
461
- StringValue(dict);
462
- BrotliEncoderPreparedDictionary* prepared_dict = BrotliEncoderPrepareDictionary(
463
- BROTLI_SHARED_DICTIONARY_RAW,
464
- (size_t)RSTRING_LEN(dict),
465
- (uint8_t*)RSTRING_PTR(dict),
466
- BROTLI_MAX_QUALITY,
467
- brotli_alloc,
468
- brotli_free,
469
- NULL);
470
- if (prepared_dict) {
471
- BrotliEncoderAttachPreparedDictionary(br->state, prepared_dict);
472
- /* Note: dict is owned by encoder after attach, no need to free */
473
- } else {
474
- rb_raise(rb_eBrotli, "Failed to prepare dictionary for compression");
475
- }
476
- }
751
+ return self;
752
+ }
753
+
754
+ static VALUE
755
+ rb_compressor_process(VALUE self, VALUE input)
756
+ {
757
+ brotli_compressor_t *br;
758
+ VALUE output;
759
+ TypedData_Get_Struct(self, brotli_compressor_t, &brotli_compressor_data_type, br);
760
+ brotli_compressor_ensure_open(br);
761
+ if (br->encoder.finished) {
762
+ rb_raise(rb_eBrotli, "Compressor is finished");
477
763
  }
764
+
765
+ StringValue(input);
766
+ output = brotli_encoder_stream_to_string(&br->encoder,
767
+ BROTLI_OPERATION_PROCESS,
768
+ (const uint8_t*)RSTRING_PTR(input),
769
+ (size_t)RSTRING_LEN(input));
770
+ RB_GC_GUARD(input);
771
+ return output;
772
+ }
773
+
774
+ static VALUE
775
+ rb_compressor_flush(VALUE self)
776
+ {
777
+ brotli_compressor_t *br;
778
+ TypedData_Get_Struct(self, brotli_compressor_t, &brotli_compressor_data_type, br);
779
+ brotli_compressor_ensure_open(br);
780
+ if (br->encoder.finished) {
781
+ rb_raise(rb_eBrotli, "Compressor is finished");
782
+ }
783
+
784
+ return brotli_encoder_stream_to_string(&br->encoder, BROTLI_OPERATION_FLUSH, NULL, 0);
785
+ }
786
+
787
+ static VALUE
788
+ rb_compressor_finish(VALUE self)
789
+ {
790
+ brotli_compressor_t *br;
791
+ VALUE output;
792
+
793
+ TypedData_Get_Struct(self, brotli_compressor_t, &brotli_compressor_data_type, br);
794
+ brotli_compressor_ensure_open(br);
795
+
796
+ if (br->encoder.finished) {
797
+ return rb_str_new("", 0);
798
+ }
799
+
800
+ output = brotli_encoder_stream_to_string(&br->encoder, BROTLI_OPERATION_FINISH, NULL, 0);
801
+ br->encoder.finished = BROTLI_TRUE;
802
+ return output;
803
+ }
804
+
805
+ static VALUE
806
+ rb_compressor_is_finished(VALUE self)
807
+ {
808
+ brotli_compressor_t *br;
809
+
810
+ TypedData_Get_Struct(self, brotli_compressor_t, &brotli_compressor_data_type, br);
811
+ brotli_compressor_ensure_open(br);
812
+ return br->encoder.finished ? Qtrue : Qfalse;
813
+ }
814
+
815
+ /*******************************************************************************
816
+ * Decompressor
817
+ ******************************************************************************/
818
+
819
+ static void
820
+ brotli_decompressor_mark(void *p)
821
+ {
822
+ brotli_decompressor_t *br = p;
823
+ #ifdef HAVE_RB_GC_MARK_MOVABLE
824
+ rb_gc_mark_movable(br->pending_input);
478
825
  #else
479
- /* Check if dictionary is requested but not supported */
480
- if (!NIL_P(opts)) {
481
- Check_Type(opts, T_HASH);
482
- dict = rb_hash_aref(opts, CSTR2SYM("dictionary"));
483
- if (!NIL_P(dict)) {
484
- rb_raise(rb_eBrotli, "Dictionary support not available in this build");
485
- }
826
+ rb_gc_mark(br->pending_input);
827
+ #endif
828
+ }
829
+
830
+ static void
831
+ brotli_decompressor_free(void *p)
832
+ {
833
+ brotli_decompressor_t *br = p;
834
+ if (br->state) {
835
+ BrotliDecoderDestroyInstance(br->state);
836
+ br->state = NULL;
837
+ }
838
+ brotli_free(NULL, br->dict_data);
839
+ br->dict_data = NULL;
840
+ br->dict_len = 0;
841
+ br->pending_input = Qnil;
842
+ br->needs_more_output = BROTLI_FALSE;
843
+ br->finished = BROTLI_FALSE;
844
+ ruby_xfree(br);
845
+ }
846
+
847
+ static void
848
+ brotli_decompressor_set_pending_input(VALUE self, brotli_decompressor_t* br, VALUE pending_input)
849
+ {
850
+ RB_OBJ_WRITE(self, &br->pending_input, pending_input);
851
+ }
852
+
853
+ static void
854
+ brotli_decompressor_reset(VALUE self, brotli_decompressor_t* br)
855
+ {
856
+ if (br->state) {
857
+ BrotliDecoderDestroyInstance(br->state);
858
+ br->state = NULL;
859
+ }
860
+
861
+ brotli_free(NULL, br->dict_data);
862
+ br->dict_data = NULL;
863
+ br->dict_len = 0;
864
+ brotli_decompressor_set_pending_input(self, br, Qnil);
865
+ br->needs_more_output = BROTLI_FALSE;
866
+ br->finished = BROTLI_FALSE;
867
+ br->state = brotli_decoder_state_create();
868
+ }
869
+
870
+ static size_t
871
+ brotli_decompressor_memsize(const void *p)
872
+ {
873
+ const brotli_decompressor_t *br = p;
874
+ return sizeof(*br) + br->dict_len;
875
+ }
876
+
877
+ #ifdef HAVE_RB_GC_MARK_MOVABLE
878
+ static void
879
+ brotli_decompressor_compact(void *p)
880
+ {
881
+ brotli_decompressor_t *br = p;
882
+
883
+ if (!NIL_P(br->pending_input)) {
884
+ br->pending_input = rb_gc_location(br->pending_input);
486
885
  }
886
+ }
487
887
  #endif
488
888
 
889
+ static const rb_data_type_t brotli_decompressor_data_type = {
890
+ "brotli_decompressor",
891
+ {
892
+ brotli_decompressor_mark,
893
+ brotli_decompressor_free,
894
+ brotli_decompressor_memsize,
895
+ #ifdef HAVE_RB_GC_MARK_MOVABLE
896
+ brotli_decompressor_compact,
897
+ #endif
898
+ },
899
+ 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
900
+ };
901
+
902
+ static VALUE
903
+ rb_decompressor_alloc(VALUE klass)
904
+ {
905
+ brotli_decompressor_t *br;
906
+ VALUE obj = TypedData_Make_Struct(klass, brotli_decompressor_t, &brotli_decompressor_data_type, br);
907
+ br->state = NULL;
908
+ br->dict_data = NULL;
909
+ br->dict_len = 0;
910
+ RB_OBJ_WRITE(obj, &br->pending_input, Qnil);
911
+ br->needs_more_output = BROTLI_FALSE;
912
+ br->finished = BROTLI_FALSE;
913
+ return obj;
914
+ }
915
+
916
+ static void
917
+ brotli_decompressor_attach_dictionary(brotli_decompressor_t* br, VALUE opts)
918
+ {
919
+ VALUE dict = brotli_hash_lookup(opts, id_dictionary);
920
+ if (NIL_P(dict)) {
921
+ return;
922
+ }
923
+
924
+ br->dict_data = brotli_copy_string_data(dict, &br->dict_len);
925
+ if (!BrotliDecoderAttachDictionary(br->state,
926
+ BROTLI_SHARED_DICTIONARY_RAW,
927
+ br->dict_len,
928
+ br->dict_data)) {
929
+ brotli_free(NULL, br->dict_data);
930
+ br->dict_data = NULL;
931
+ br->dict_len = 0;
932
+ rb_raise(rb_eBrotli, "Failed to attach dictionary for decompression");
933
+ }
934
+ }
935
+
936
+ static VALUE
937
+ rb_decompressor_initialize(int argc, VALUE* argv, VALUE self)
938
+ {
939
+ VALUE opts = Qnil;
940
+ brotli_decompressor_t *br;
941
+
942
+ rb_scan_args(argc, argv, "01", &opts);
943
+ TypedData_Get_Struct(self, brotli_decompressor_t, &brotli_decompressor_data_type, br);
944
+ brotli_decompressor_reset(self, br);
945
+ brotli_decompressor_attach_dictionary(br, opts);
946
+
489
947
  return self;
490
948
  }
491
949
 
492
- static VALUE rb_writer_write(VALUE self, VALUE buf) {
493
- struct brotli* br;
494
- TypedData_Get_Struct(self, struct brotli, &brotli_data_type, br);
495
- StringValue(buf);
950
+ static BROTLI_BOOL
951
+ brotli_decompressor_has_pending_input(const brotli_decompressor_t *br)
952
+ {
953
+ return !NIL_P(br->pending_input) && RSTRING_LEN(br->pending_input) > 0;
954
+ }
496
955
 
497
- const size_t total = (size_t)RSTRING_LEN(buf);
956
+ static size_t
957
+ brotli_decompressor_pending_input_length(const brotli_decompressor_t *br)
958
+ {
959
+ if (!brotli_decompressor_has_pending_input(br)) {
960
+ return 0;
961
+ }
962
+ return (size_t)RSTRING_LEN(br->pending_input);
963
+ }
498
964
 
499
- brotli_encoder_args_t args = {
500
- .state = br->state,
501
- .op = BROTLI_OPERATION_PROCESS,
502
- .available_in = total,
503
- .next_in = (uint8_t*)RSTRING_PTR(buf)
504
- };
965
+ static void
966
+ brotli_decompressor_clear_pending_input(VALUE self, brotli_decompressor_t* br)
967
+ {
968
+ brotli_decompressor_set_pending_input(self, br, Qnil);
969
+ }
505
970
 
506
- while (args.available_in > 0) {
507
- rb_thread_call_without_gvl(compress_no_gvl, (void*)&args, NULL, NULL);
508
- push_output(br);
971
+ static void
972
+ brotli_decompressor_ensure_open(const brotli_decompressor_t* br)
973
+ {
974
+ if (!br->state) {
975
+ rb_raise(rb_eBrotli, "Decompressor is closed");
509
976
  }
977
+ }
978
+
979
+ static BROTLI_BOOL
980
+ brotli_decompressor_needs_output_drain(const brotli_decompressor_t *br)
981
+ {
982
+ return br->needs_more_output || brotli_decompressor_has_pending_input(br) ||
983
+ BrotliDecoderHasMoreOutput(br->state);
984
+ }
510
985
 
511
- return SIZET2NUM(total);
986
+ static void
987
+ brotli_decompressor_store_pending_input(VALUE self,
988
+ brotli_decompressor_t* br,
989
+ const uint8_t* next_in,
990
+ size_t available_in)
991
+ {
992
+ if (available_in == 0) {
993
+ brotli_decompressor_clear_pending_input(self, br);
994
+ return;
995
+ }
996
+ brotli_decompressor_set_pending_input(self, br, rb_str_new((const char*)next_in, (long)available_in));
512
997
  }
513
998
 
514
- static VALUE rb_writer_finish(VALUE self) {
515
- struct brotli* br;
516
- TypedData_Get_Struct(self, struct brotli, &brotli_data_type, br);
999
+ static size_t
1000
+ brotli_output_buffer_limit(VALUE opts)
1001
+ {
1002
+ VALUE limit_value = brotli_hash_lookup(opts, id_output_buffer_limit);
517
1003
 
518
- brotli_encoder_args_t args = {
519
- .state = br->state,
520
- .op = BROTLI_OPERATION_FINISH,
521
- .available_in = 0,
522
- .next_in = NULL
523
- };
1004
+ if (NIL_P(limit_value)) {
1005
+ return 0;
1006
+ }
524
1007
 
525
- while (!BrotliEncoderIsFinished(br->state)) {
526
- rb_thread_call_without_gvl(compress_no_gvl, (void*)&args, NULL, NULL);
527
- push_output(br);
1008
+ if (rb_cmpint(rb_funcall(limit_value, id_cmp, 1, INT2FIX(0)), limit_value, INT2FIX(0)) <= 0) {
1009
+ rb_raise(rb_eArgError, "output_buffer_limit must be positive");
528
1010
  }
529
- return br->io;
1011
+
1012
+ return NUM2SIZET(limit_value);
530
1013
  }
531
1014
 
532
- static VALUE rb_writer_flush(VALUE self) {
533
- struct brotli *br;
534
- TypedData_Get_Struct(self, struct brotli, &brotli_data_type, br);
1015
+ static VALUE
1016
+ rb_decompressor_process(int argc, VALUE* argv, VALUE self)
1017
+ {
1018
+ brotli_decompressor_t *br;
1019
+ VALUE input = Qnil;
1020
+ VALUE opts = Qnil;
1021
+ VALUE input_source = Qnil;
1022
+ VALUE output;
1023
+ size_t available_in;
1024
+ const uint8_t* next_in;
1025
+ size_t output_buffer_limit;
1026
+ BROTLI_BOOL limit_output;
1027
+ BrotliDecoderResult result = BROTLI_DECODER_RESULT_ERROR;
1028
+ uint8_t outbuf[BUFSIZ];
1029
+
1030
+ rb_scan_args(argc, argv, "11", &input, &opts);
1031
+ TypedData_Get_Struct(self, brotli_decompressor_t, &brotli_decompressor_data_type, br);
1032
+ brotli_decompressor_ensure_open(br);
1033
+ output_buffer_limit = brotli_output_buffer_limit(opts);
1034
+ limit_output = output_buffer_limit > 0 ? BROTLI_TRUE : BROTLI_FALSE;
1035
+
1036
+ if (br->finished) {
1037
+ StringValue(input);
1038
+ available_in = (size_t)RSTRING_LEN(input);
1039
+ if (available_in == 0) {
1040
+ return rb_str_buf_new(0);
1041
+ }
1042
+ rb_raise(rb_eBrotli, "Decompressor is finished");
1043
+ }
535
1044
 
536
- brotli_encoder_args_t args = {
537
- .state = br->state,
538
- .op = BROTLI_OPERATION_FLUSH,
539
- .available_in = 0,
540
- .next_in = NULL
541
- };
1045
+ StringValue(input);
1046
+ if (brotli_decompressor_needs_output_drain(br)) {
1047
+ if (RSTRING_LEN(input) > 0) {
1048
+ rb_raise(rb_eBrotli,
1049
+ "Decompressor cannot accept more data until pending output is drained");
1050
+ }
1051
+ if (brotli_decompressor_has_pending_input(br)) {
1052
+ input_source = br->pending_input;
1053
+ } else {
1054
+ input_source = input;
1055
+ }
1056
+ } else {
1057
+ input_source = input;
1058
+ }
1059
+
1060
+ if (input_source == br->pending_input) {
1061
+ available_in = brotli_decompressor_pending_input_length(br);
1062
+ next_in = (const uint8_t*)RSTRING_PTR(input_source);
1063
+ } else {
1064
+ available_in = (size_t)RSTRING_LEN(input_source);
1065
+ next_in = (const uint8_t*)RSTRING_PTR(input_source);
1066
+ brotli_decompressor_clear_pending_input(self, br);
1067
+ }
1068
+ br->needs_more_output = BROTLI_FALSE;
1069
+ output = rb_str_buf_new(BUFSIZ);
1070
+
1071
+ for (;;) {
1072
+ size_t chunk_size = BUFSIZ;
1073
+ brotli_decoder_args_t args;
1074
+ size_t produced;
1075
+
1076
+ if (limit_output) {
1077
+ size_t used = (size_t)RSTRING_LEN(output);
1078
+ if (used >= output_buffer_limit) {
1079
+ brotli_decompressor_store_pending_input(self, br, next_in, available_in);
1080
+ br->needs_more_output = BROTLI_TRUE;
1081
+ break;
1082
+ }
1083
+ if (output_buffer_limit - used < chunk_size) {
1084
+ chunk_size = output_buffer_limit - used;
1085
+ }
1086
+ }
542
1087
 
543
- do {
544
- rb_thread_call_without_gvl(compress_no_gvl, (void*)&args, NULL, NULL);
545
- push_output(br);
546
- } while (BrotliEncoderHasMoreOutput(br->state));
1088
+ args.state = br->state;
1089
+ args.available_in = available_in;
1090
+ args.next_in = next_in;
1091
+ args.available_out = chunk_size;
1092
+ args.next_out = outbuf;
1093
+ args.result = BROTLI_DECODER_RESULT_ERROR;
1094
+ brotli_decompressor_step(&args);
1095
+ available_in = args.available_in;
1096
+ next_in = args.next_in;
1097
+ result = args.result;
1098
+
1099
+ produced = chunk_size - args.available_out;
1100
+ if (produced > 0) {
1101
+ rb_str_cat(output, (const char*)outbuf, produced);
1102
+ }
547
1103
 
548
- if (rb_respond_to(br->io, id_flush)) {
549
- rb_funcall(br->io, id_flush, 0);
1104
+ if (result == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) {
1105
+ if (limit_output && (size_t)RSTRING_LEN(output) >= output_buffer_limit) {
1106
+ brotli_decompressor_store_pending_input(self, br, next_in, available_in);
1107
+ br->needs_more_output = BROTLI_TRUE;
1108
+ break;
1109
+ }
1110
+ continue;
1111
+ }
1112
+ if (result == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT) {
1113
+ if (BrotliDecoderHasMoreOutput(br->state)) {
1114
+ continue;
1115
+ }
1116
+ brotli_decompressor_clear_pending_input(self, br);
1117
+ br->needs_more_output = BROTLI_FALSE;
1118
+ break;
1119
+ }
1120
+ if (result == BROTLI_DECODER_RESULT_SUCCESS) {
1121
+ br->finished = BROTLI_TRUE;
1122
+ brotli_decompressor_store_pending_input(self, br, next_in, available_in);
1123
+ br->needs_more_output = BROTLI_FALSE;
1124
+ break;
1125
+ }
1126
+
1127
+ rb_raise(rb_eBrotli, "%s",
1128
+ BrotliDecoderErrorString(BrotliDecoderGetErrorCode(br->state)));
550
1129
  }
551
- return self;
1130
+
1131
+ RB_GC_GUARD(input_source);
1132
+ return output;
552
1133
  }
553
1134
 
554
- static VALUE rb_writer_close(VALUE self) {
555
- struct brotli* br;
556
- TypedData_Get_Struct(self, struct brotli, &brotli_data_type, br);
1135
+ static VALUE
1136
+ rb_decompressor_is_finished(VALUE self)
1137
+ {
1138
+ brotli_decompressor_t *br;
1139
+
1140
+ TypedData_Get_Struct(self, brotli_decompressor_t, &brotli_decompressor_data_type, br);
1141
+ brotli_decompressor_ensure_open(br);
1142
+
1143
+ if (br->finished || BrotliDecoderIsFinished(br->state)) {
1144
+ br->finished = BROTLI_TRUE;
1145
+ return Qtrue;
1146
+ }
1147
+ return Qfalse;
1148
+ }
557
1149
 
558
- rb_writer_finish(self);
1150
+ static VALUE
1151
+ rb_decompressor_can_accept_more_data(VALUE self)
1152
+ {
1153
+ brotli_decompressor_t *br;
1154
+ TypedData_Get_Struct(self, brotli_decompressor_t, &brotli_decompressor_data_type, br);
1155
+ brotli_decompressor_ensure_open(br);
1156
+ return (br->finished || brotli_decompressor_needs_output_drain(br)) ? Qfalse : Qtrue;
1157
+ }
559
1158
 
560
- if (rb_respond_to(br->io, id_close)) {
561
- rb_funcall(br->io, id_close, 0);
1159
+ static VALUE
1160
+ rb_decompressor_unused_data(VALUE self)
1161
+ {
1162
+ brotli_decompressor_t *br;
1163
+ size_t available_in;
1164
+ TypedData_Get_Struct(self, brotli_decompressor_t, &brotli_decompressor_data_type, br);
1165
+ brotli_decompressor_ensure_open(br);
1166
+ available_in = brotli_decompressor_pending_input_length(br);
1167
+ if (!br->finished || available_in == 0) {
1168
+ return rb_str_buf_new(0);
562
1169
  }
563
- return br->io;
1170
+ return rb_str_dup(br->pending_input);
564
1171
  }
565
1172
 
566
1173
  /*******************************************************************************
@@ -570,28 +1177,41 @@ static VALUE rb_writer_close(VALUE self) {
570
1177
  void
571
1178
  Init_brotli(void)
572
1179
  {
573
- #if HAVE_RB_EXT_RACTOR_SAFE
574
1180
  rb_ext_ractor_safe(true);
575
- #endif
576
1181
 
577
1182
  VALUE rb_mBrotli;
578
- VALUE rb_Writer;
579
1183
  rb_mBrotli = rb_define_module("Brotli");
580
1184
  rb_eBrotli = rb_define_class_under(rb_mBrotli, "Error", rb_eStandardError);
581
- rb_global_variable(&rb_eBrotli);
582
1185
  rb_define_singleton_method(rb_mBrotli, "deflate", brotli_deflate, -1);
583
1186
  rb_define_singleton_method(rb_mBrotli, "inflate", brotli_inflate, -1);
584
1187
  rb_define_singleton_method(rb_mBrotli, "version", brotli_version, 0);
1188
+ id_cmp = rb_intern("<=>");
1189
+ id_dictionary = rb_intern("dictionary");
1190
+ id_font = rb_intern("font");
1191
+ id_generic = rb_intern("generic");
1192
+ id_lgblock = rb_intern("lgblock");
1193
+ id_lgwin = rb_intern("lgwin");
1194
+ id_mode = rb_intern("mode");
1195
+ id_output_buffer_limit = rb_intern("output_buffer_limit");
1196
+ id_quality = rb_intern("quality");
585
1197
  id_read = rb_intern("read");
586
- // Brotli::Writer
587
- id_write = rb_intern("write");
588
- id_flush = rb_intern("flush");
589
- id_close = rb_intern("close");
590
- rb_Writer = rb_define_class_under(rb_mBrotli, "Writer", rb_cObject);
591
- rb_define_alloc_func(rb_Writer, rb_writer_alloc);
592
- rb_define_method(rb_Writer, "initialize", rb_writer_initialize, -1);
593
- rb_define_method(rb_Writer, "write", rb_writer_write, 1);
594
- rb_define_method(rb_Writer, "finish", rb_writer_finish, 0);
595
- rb_define_method(rb_Writer, "flush", rb_writer_flush, 0);
596
- rb_define_method(rb_Writer, "close", rb_writer_close, 0);
1198
+ id_text = rb_intern("text");
1199
+
1200
+ rb_cBrotliCompressor = rb_define_class_under(rb_mBrotli, "Compressor", rb_cObject);
1201
+ rb_define_alloc_func(rb_cBrotliCompressor, rb_compressor_alloc);
1202
+ rb_define_method(rb_cBrotliCompressor, "initialize", rb_compressor_initialize, -1);
1203
+ rb_define_method(rb_cBrotliCompressor, "process", rb_compressor_process, 1);
1204
+ rb_define_method(rb_cBrotliCompressor, "flush", rb_compressor_flush, 0);
1205
+ rb_define_method(rb_cBrotliCompressor, "finish", rb_compressor_finish, 0);
1206
+ rb_define_method(rb_cBrotliCompressor, "finished?", rb_compressor_is_finished, 0);
1207
+
1208
+ rb_cBrotliDecompressor = rb_define_class_under(rb_mBrotli, "Decompressor", rb_cObject);
1209
+ rb_define_alloc_func(rb_cBrotliDecompressor, rb_decompressor_alloc);
1210
+ rb_define_method(rb_cBrotliDecompressor, "initialize", rb_decompressor_initialize, -1);
1211
+ rb_define_method(rb_cBrotliDecompressor, "process", rb_decompressor_process, -1);
1212
+ rb_define_method(rb_cBrotliDecompressor, "is_finished", rb_decompressor_is_finished, 0);
1213
+ rb_define_method(rb_cBrotliDecompressor, "finished?", rb_decompressor_is_finished, 0);
1214
+ rb_define_method(rb_cBrotliDecompressor, "can_accept_more_data", rb_decompressor_can_accept_more_data, 0);
1215
+ rb_define_method(rb_cBrotliDecompressor, "can_accept_more_data?", rb_decompressor_can_accept_more_data, 0);
1216
+ rb_define_method(rb_cBrotliDecompressor, "unused_data", rb_decompressor_unused_data, 0);
597
1217
  }