Class: VibeZstd::DCtx
- Inherits:
-
Object
- Object
- VibeZstd::DCtx
- Defined in:
- ext/vibe_zstd/vibe_zstd.c
Class Method Summary collapse
-
.default_initial_capacity ⇒ Object
DCtx default_initial_capacity getter (class method).
-
.default_initial_capacity=(value) ⇒ Object
DCtx default_initial_capacity setter (class method).
-
.default_max_decompressed_size ⇒ Object
DCtx default_max_decompressed_size getter (class method); 0 = unlimited.
-
.default_max_decompressed_size=(value) ⇒ Object
DCtx default_max_decompressed_size setter (class method).
-
.estimate_memory ⇒ Object
DCtx.estimate_memory().
-
.frame_content_size(data) ⇒ Object
DCtx frame_content_size - class method to get frame content size.
- .parameter_bounds ⇒ Object
Instance Method Summary collapse
-
#decompress(*args) ⇒ Object
Skippable frames at the beginning of data are automatically skipped.
- #format ⇒ Object
- #format= ⇒ Object
-
#initial_capacity ⇒ Object
DCtx initial_capacity getter (instance method).
-
#initial_capacity=(value) ⇒ Object
DCtx initial_capacity setter (instance method).
-
#max_decompressed_size ⇒ Object
(also: #max_size)
limit, falling back to the class default.
-
#max_decompressed_size=(value) ⇒ Object
(also: #max_size=)
DCtx max_decompressed_size setter (instance method); nil = inherit class default.
-
#reset(*args) ⇒ Object
DCtx reset - reset context to clean state.
-
#use_prefix(prefix_data) ⇒ Object
DCtx use_prefix - use raw data as prefix (lightweight dictionary).
- #window_log_max ⇒ Object (also: #max_window_log)
-
#window_log_max= ⇒ Object
(also: #max_window_log=)
DCtx parameter accessors.
Class Method Details
.default_initial_capacity ⇒ Object
DCtx default_initial_capacity getter (class method)
181 182 183 184 185 186 187 |
# File 'ext/vibe_zstd/dctx.c', line 181
static VALUE
vibe_zstd_dctx_get_default_initial_capacity(VALUE self) {
if (default_initial_capacity == 0) {
return SIZET2NUM(ZSTD_DStreamOutSize());
}
return SIZET2NUM(default_initial_capacity);
}
|
.default_initial_capacity=(value) ⇒ Object
DCtx default_initial_capacity setter (class method)
190 191 192 193 194 195 196 197 198 199 200 201 202 |
# File 'ext/vibe_zstd/dctx.c', line 190
static VALUE
vibe_zstd_dctx_set_default_initial_capacity(VALUE self, VALUE value) {
if (NIL_P(value)) {
default_initial_capacity = 0; // Reset to default
} else {
size_t capacity = NUM2SIZET(value);
if (capacity == 0) {
rb_raise(rb_eArgError, "initial_capacity must be positive (or nil to reset to default)");
}
default_initial_capacity = capacity;
}
return value;
}
|
.default_max_decompressed_size ⇒ Object
DCtx default_max_decompressed_size getter (class method); 0 = unlimited
236 237 238 239 |
# File 'ext/vibe_zstd/dctx.c', line 236 static VALUE vibe_zstd_dctx_get_default_max_decompressed_size(VALUE self) { return SIZET2NUM(default_max_decompressed_size); } |
.default_max_decompressed_size=(value) ⇒ Object
DCtx default_max_decompressed_size setter (class method)
242 243 244 245 246 247 248 249 250 |
# File 'ext/vibe_zstd/dctx.c', line 242
static VALUE
vibe_zstd_dctx_set_default_max_decompressed_size(VALUE self, VALUE value) {
if (NIL_P(value)) {
default_max_decompressed_size = 0; // unlimited
} else {
default_max_decompressed_size = NUM2SIZET(value);
}
return value;
}
|
.estimate_memory ⇒ Object
DCtx.estimate_memory()
55 56 57 58 59 |
# File 'ext/vibe_zstd/dctx.c', line 55 static VALUE vibe_zstd_dctx_estimate_memory(VALUE self) { size_t estimate = ZSTD_estimateDCtxSize(); return SIZET2NUM(estimate); } |
.frame_content_size(data) ⇒ Object
DCtx frame_content_size - class method to get frame content size
461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 |
# File 'ext/vibe_zstd/dctx.c', line 461
static VALUE
vibe_zstd_dctx_frame_content_size(VALUE self, VALUE data) {
StringValue(data);
unsigned long long contentSize = ZSTD_getFrameContentSize(RSTRING_PTR(data), RSTRING_LEN(data));
if (contentSize == ZSTD_CONTENTSIZE_ERROR) {
return Qnil; // Invalid frame
}
if (contentSize == ZSTD_CONTENTSIZE_UNKNOWN) {
return Qnil; // Unknown size
}
return ULL2NUM(contentSize);
}
|
.parameter_bounds ⇒ Object
Instance Method Details
#decompress(*args) ⇒ Object
Skippable frames at the beginning of data are automatically skipped.
489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 |
# File 'ext/vibe_zstd/dctx.c', line 489
static VALUE
vibe_zstd_dctx_decompress(int argc, VALUE* argv, VALUE self) {
VALUE data, options = Qnil;
rb_scan_args(argc, argv, "1:", &data, &options);
vibe_zstd_dctx* dctx;
TypedData_Get_Struct(self, vibe_zstd_dctx, &vibe_zstd_dctx_type, dctx);
StringValue(data);
const char* src = RSTRING_PTR(data);
size_t srcSize = RSTRING_LEN(data);
size_t offset = 0;
// Magicless frames (format = ZSTD_f_zstd1_magicless) carry no magic number,
// so frame introspection (content size, dict ID, skippable detection) cannot
// be performed. Force the streaming decompress path, which honors the format
// parameter set on the context via ZSTD_decompressStream.
int dformat = 0;
(void)ZSTD_DCtx_getParameter(dctx->dctx, ZSTD_d_format, &dformat);
int magicless = (dformat == ZSTD_f_zstd1_magicless);
unsigned long long contentSize;
unsigned int frame_dict_id;
if (magicless) {
contentSize = ZSTD_CONTENTSIZE_UNKNOWN; // route to streaming path
frame_dict_id = 0; // cannot read dict ID without magic
} else {
// Skip any leading skippable frames
while (offset < srcSize && ZSTD_isSkippableFrame(src + offset, srcSize - offset)) {
size_t frameSize = ZSTD_findFrameCompressedSize(src + offset, srcSize - offset);
if (ZSTD_isError(frameSize)) {
rb_raise(rb_eRuntimeError, "Invalid skippable frame at offset %zu: %s", offset, ZSTD_getErrorName(frameSize));
}
offset += frameSize;
}
// Now check the actual compressed frame
if (offset >= srcSize) {
rb_raise(rb_eRuntimeError, "No compressed frame found in %zu bytes (only skippable frames)", srcSize);
}
src += offset;
srcSize -= offset;
contentSize = ZSTD_getFrameContentSize(src, srcSize);
if (contentSize == ZSTD_CONTENTSIZE_ERROR) {
rb_raise(rb_eRuntimeError, "Invalid compressed data: not a valid zstd frame (size: %zu bytes)", srcSize);
}
// Check dictionary requirements from the frame
frame_dict_id = ZSTD_getDictID_fromFrame(src, srcSize);
}
// Extract keyword arguments
ZSTD_DDict* ddict = NULL;
unsigned int provided_dict_id = 0;
size_t initial_capacity = 0; // 0 = not specified in per-call options
size_t max_size = 0; // 0 = not specified in per-call options
if (!NIL_P(options)) {
VALUE dict_val = rb_hash_aref(options, ID2SYM(rb_intern("dict")));
if (!NIL_P(dict_val)) {
vibe_zstd_ddict* ddict_struct;
TypedData_Get_Struct(dict_val, vibe_zstd_ddict, &vibe_zstd_ddict_type, ddict_struct);
ddict = ddict_struct->ddict;
provided_dict_id = ZSTD_getDictID_fromDDict(ddict);
}
VALUE initial_capacity_val = rb_hash_aref(options, ID2SYM(rb_intern("initial_capacity")));
if (!NIL_P(initial_capacity_val)) {
initial_capacity = NUM2SIZET(initial_capacity_val);
if (initial_capacity == 0) {
rb_raise(rb_eArgError, "initial_capacity must be positive");
}
}
// Per-call output-size limit; accepts :max_decompressed_size or :max_size.
VALUE max_size_val = rb_hash_aref(options, ID2SYM(rb_intern("max_decompressed_size")));
if (NIL_P(max_size_val)) {
max_size_val = rb_hash_aref(options, ID2SYM(rb_intern("max_size")));
}
if (!NIL_P(max_size_val)) {
max_size = NUM2SIZET(max_size_val);
if (max_size == 0) {
rb_raise(rb_eArgError, "max_decompressed_size must be positive");
}
}
}
// Resolve max_size fallback chain: per-call > instance > class default.
// A value of 0 at every level means unlimited.
if (max_size == 0) {
max_size = dctx->max_decompressed_size; // instance
if (max_size == 0) {
max_size = default_max_decompressed_size; // class
}
}
// Validate dictionary matches frame requirements
if (frame_dict_id != 0 && ddict == NULL) {
rb_raise(rb_eArgError, "Data requires dictionary (dict_id: %u) but none provided", frame_dict_id);
}
if (ddict != NULL && frame_dict_id != 0 && provided_dict_id != frame_dict_id) {
rb_raise(rb_eArgError, "Dictionary mismatch: frame requires dict_id %u, provided dict_id %u",
frame_dict_id, provided_dict_id);
}
// Resolve initial_capacity fallback chain: per-call > instance > class default > ZSTD default
if (initial_capacity == 0) {
initial_capacity = dctx->initial_capacity; // Instance default
if (initial_capacity == 0) {
initial_capacity = default_initial_capacity; // Class default
if (initial_capacity == 0) {
initial_capacity = ZSTD_DStreamOutSize(); // ZSTD default (~128KB)
}
}
}
// If content size is unknown, use streaming decompression with exponential growth.
// Releases GVL to allow other Ruby threads to run during decompression.
// Uses C malloc/realloc (not Ruby allocators) since Ruby API calls are forbidden without GVL.
if (contentSize == ZSTD_CONTENTSIZE_UNKNOWN) {
// Reference the dictionary on the context before streaming decompression.
// ZSTD_decompressStream uses whatever dict is referenced on the DCtx, so
// without this the dictionary would be ignored on the unknown-size path
// (every dict frame produced by CompressWriter has unknown content size).
if (ddict) {
size_t rd = ZSTD_DCtx_refDDict(dctx->dctx, ddict);
if (ZSTD_isError(rd)) {
rb_raise(rb_eRuntimeError, "Failed to reference dictionary: %s", ZSTD_getErrorName(rd));
}
}
decompress_stream_nogvl_args stream_args = {
.dctx = dctx->dctx,
.src = src,
.src_size = srcSize,
.dst = NULL,
.dst_capacity = 0,
.dst_size = 0,
.initial_capacity = initial_capacity,
.max_size = max_size,
.error = 0,
.limit_exceeded = 0,
.truncated = 0,
.error_name = NULL
};
// Run the streaming decompression and build the result under rb_ensure:
// the cleanup frees the C buffer and un-references the dictionary on
// every exit path, including the raises below and async exceptions
// delivered when the GVL is reacquired.
dctx_stream_decompress_state state = {
.dctx = dctx->dctx,
.ddict = ddict,
.args = &stream_args,
.data = data,
.max_size = max_size
};
return rb_ensure(vibe_zstd_dctx_stream_decompress_body, (VALUE)&state,
vibe_zstd_dctx_stream_decompress_cleanup, (VALUE)&state);
}
// Reject a frame whose declared content size exceeds the limit before
// allocating the output buffer (the header is attacker-controlled).
if (max_size && contentSize > (unsigned long long)max_size) {
rb_raise(rb_eDecompressedSizeExceeded,
"Declared content size %llu exceeds limit of %zu bytes", contentSize, max_size);
}
VALUE result = rb_str_new(NULL, contentSize);
decompress_args args = {
.dctx = dctx->dctx,
.ddict = ddict,
.src = src,
.srcSize = srcSize,
.dst = RSTRING_PTR(result),
.dstCapacity = contentSize,
.result = 0
};
// Lock the source string while the GVL is released: another Ruby thread
// holding the same string must not mutate or GC it mid-decompression.
// The helper unlocks via rb_ensure so an async exception cannot leave
// the string permanently locked.
vibe_zstd_nogvl_with_str_locked(decompress_without_gvl, &args, data);
if (ZSTD_isError(args.result)) {
rb_raise(rb_eRuntimeError, "Decompression failed: %s", ZSTD_getErrorName(args.result));
}
rb_str_set_len(result, args.result);
return result;
}
|
#format ⇒ Object
#format= ⇒ Object
#initial_capacity ⇒ Object
DCtx initial_capacity getter (instance method)
205 206 207 208 209 210 211 212 213 214 215 |
# File 'ext/vibe_zstd/dctx.c', line 205
static VALUE
vibe_zstd_dctx_get_initial_capacity(VALUE self) {
vibe_zstd_dctx* dctx;
TypedData_Get_Struct(self, vibe_zstd_dctx, &vibe_zstd_dctx_type, dctx);
if (dctx->initial_capacity == 0) {
// Return the class default
return vibe_zstd_dctx_get_default_initial_capacity(Qnil);
}
return SIZET2NUM(dctx->initial_capacity);
}
|
#initial_capacity=(value) ⇒ Object
DCtx initial_capacity setter (instance method)
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 |
# File 'ext/vibe_zstd/dctx.c', line 218
static VALUE
vibe_zstd_dctx_set_initial_capacity(VALUE self, VALUE value) {
vibe_zstd_dctx* dctx;
TypedData_Get_Struct(self, vibe_zstd_dctx, &vibe_zstd_dctx_type, dctx);
if (NIL_P(value)) {
dctx->initial_capacity = 0; // Use class default
} else {
size_t capacity = NUM2SIZET(value);
if (capacity == 0) {
rb_raise(rb_eArgError, "initial_capacity must be positive (or nil to use class default)");
}
dctx->initial_capacity = capacity;
}
return value;
}
|
#max_decompressed_size ⇒ Object Also known as: max_size
limit, falling back to the class default. Returns 0 when unlimited.
254 255 256 257 258 259 260 261 262 263 |
# File 'ext/vibe_zstd/dctx.c', line 254
static VALUE
vibe_zstd_dctx_get_max_decompressed_size(VALUE self) {
vibe_zstd_dctx* dctx;
TypedData_Get_Struct(self, vibe_zstd_dctx, &vibe_zstd_dctx_type, dctx);
if (dctx->max_decompressed_size == 0) {
return SIZET2NUM(default_max_decompressed_size);
}
return SIZET2NUM(dctx->max_decompressed_size);
}
|
#max_decompressed_size=(value) ⇒ Object Also known as: max_size=
DCtx max_decompressed_size setter (instance method); nil = inherit class default
266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 |
# File 'ext/vibe_zstd/dctx.c', line 266
static VALUE
vibe_zstd_dctx_set_max_decompressed_size(VALUE self, VALUE value) {
vibe_zstd_dctx* dctx;
TypedData_Get_Struct(self, vibe_zstd_dctx, &vibe_zstd_dctx_type, dctx);
if (NIL_P(value)) {
dctx->max_decompressed_size = 0; // inherit class default
} else {
size_t limit = NUM2SIZET(value);
if (limit == 0) {
rb_raise(rb_eArgError, "max_decompressed_size must be positive (or nil to inherit the class default)");
}
dctx->max_decompressed_size = limit;
}
return value;
}
|
#reset(*args) ⇒ Object
DCtx reset - reset context to clean state
698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 |
# File 'ext/vibe_zstd/dctx.c', line 698
static VALUE
vibe_zstd_dctx_reset(int argc, VALUE* argv, VALUE self) {
VALUE reset_mode;
rb_scan_args(argc, argv, "01", &reset_mode);
vibe_zstd_dctx* dctx;
TypedData_Get_Struct(self, vibe_zstd_dctx, &vibe_zstd_dctx_type, dctx);
// Default to SESSION_AND_PARAMETERS if no argument provided
ZSTD_ResetDirective directive = ZSTD_reset_session_and_parameters;
if (!NIL_P(reset_mode)) {
int mode = NUM2INT(reset_mode);
if (mode == ZSTD_reset_session_only) {
directive = ZSTD_reset_session_only;
} else if (mode == ZSTD_reset_parameters) {
directive = ZSTD_reset_parameters;
} else if (mode == ZSTD_reset_session_and_parameters) {
directive = ZSTD_reset_session_and_parameters;
} else {
rb_raise(rb_eArgError, "Invalid reset_mode %d: must be ResetDirective::SESSION (1), PARAMETERS (2), or BOTH (3)", mode);
}
}
size_t result = ZSTD_DCtx_reset(dctx->dctx, directive);
if (ZSTD_isError(result)) {
rb_raise(rb_eRuntimeError, "Failed to reset decompression context: %s", ZSTD_getErrorName(result));
}
return self;
}
|
#use_prefix(prefix_data) ⇒ Object
DCtx use_prefix - use raw data as prefix (lightweight dictionary)
681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 |
# File 'ext/vibe_zstd/dctx.c', line 681
static VALUE
vibe_zstd_dctx_use_prefix(VALUE self, VALUE prefix_data) {
vibe_zstd_dctx* dctx;
TypedData_Get_Struct(self, vibe_zstd_dctx, &vibe_zstd_dctx_type, dctx);
StringValue(prefix_data);
size_t result = ZSTD_DCtx_refPrefix(dctx->dctx, RSTRING_PTR(prefix_data), RSTRING_LEN(prefix_data));
if (ZSTD_isError(result)) {
rb_raise(rb_eRuntimeError, "Failed to set prefix: %s", ZSTD_getErrorName(result));
}
return self;
}
|
#window_log_max ⇒ Object Also known as: max_window_log
#window_log_max= ⇒ Object Also known as: max_window_log=
DCtx parameter accessors