aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/Modules/_zstd/decompressor.c
diff options
context:
space:
mode:
Diffstat (limited to 'Modules/_zstd/decompressor.c')
-rw-r--r--Modules/_zstd/decompressor.c891
1 files changed, 891 insertions, 0 deletions
diff --git a/Modules/_zstd/decompressor.c b/Modules/_zstd/decompressor.c
new file mode 100644
index 00000000000..4e3a28068be
--- /dev/null
+++ b/Modules/_zstd/decompressor.c
@@ -0,0 +1,891 @@
+/*
+Low level interface to Meta's zstd library for use in the compression.zstd
+Python module.
+*/
+
+/* ZstdDecompressor class definition */
+
+/*[clinic input]
+module _zstd
+class _zstd.ZstdDecompressor "ZstdDecompressor *" "clinic_state()->ZstdDecompressor_type"
+[clinic start generated code]*/
+/*[clinic end generated code: output=da39a3ee5e6b4b0d input=4e6eae327c0c0c76]*/
+
+#ifndef Py_BUILD_CORE_BUILTIN
+# define Py_BUILD_CORE_MODULE 1
+#endif
+
+#include "_zstdmodule.h"
+
+#include "buffer.h"
+
+#include <stddef.h> // offsetof()
+
+#define ZstdDecompressor_CAST(op) ((ZstdDecompressor *)op)
+
+static inline ZSTD_DDict *
+_get_DDict(ZstdDict *self)
+{
+ ZSTD_DDict *ret;
+
+ /* Already created */
+ if (self->d_dict != NULL) {
+ return self->d_dict;
+ }
+
+ Py_BEGIN_CRITICAL_SECTION(self);
+ if (self->d_dict == NULL) {
+ /* Create ZSTD_DDict instance from dictionary content */
+ char *dict_buffer = PyBytes_AS_STRING(self->dict_content);
+ Py_ssize_t dict_len = Py_SIZE(self->dict_content);
+ Py_BEGIN_ALLOW_THREADS
+ self->d_dict = ZSTD_createDDict(dict_buffer,
+ dict_len);
+ Py_END_ALLOW_THREADS
+
+ if (self->d_dict == NULL) {
+ _zstd_state* const mod_state = PyType_GetModuleState(Py_TYPE(self));
+ if (mod_state != NULL) {
+ PyErr_SetString(mod_state->ZstdError,
+ "Failed to create ZSTD_DDict instance from zstd "
+ "dictionary content. Maybe the content is corrupted.");
+ }
+ }
+ }
+
+ /* Don't lose any exception */
+ ret = self->d_dict;
+ Py_END_CRITICAL_SECTION();
+
+ return ret;
+}
+
+/* Set decompression parameters to decompression context */
+int
+_PyZstd_set_d_parameters(ZstdDecompressor *self, PyObject *options)
+{
+ size_t zstd_ret;
+ PyObject *key, *value;
+ Py_ssize_t pos;
+ _zstd_state* const mod_state = PyType_GetModuleState(Py_TYPE(self));
+ if (mod_state == NULL) {
+ return -1;
+ }
+
+ if (!PyDict_Check(options)) {
+ PyErr_SetString(PyExc_TypeError,
+ "options argument should be dict object.");
+ return -1;
+ }
+
+ pos = 0;
+ while (PyDict_Next(options, &pos, &key, &value)) {
+ /* Check key type */
+ if (Py_TYPE(key) == mod_state->CParameter_type) {
+ PyErr_SetString(PyExc_TypeError,
+ "Key of decompression options dict should "
+ "NOT be CParameter.");
+ return -1;
+ }
+
+ /* Both key & value should be 32-bit signed int */
+ int key_v = PyLong_AsInt(key);
+ if (key_v == -1 && PyErr_Occurred()) {
+ PyErr_SetString(PyExc_ValueError,
+ "Key of options dict should be a DParameter attribute.");
+ return -1;
+ }
+
+ // TODO(emmatyping): check bounds when there is a value error here for better
+ // error message?
+ int value_v = PyLong_AsInt(value);
+ if (value_v == -1 && PyErr_Occurred()) {
+ PyErr_SetString(PyExc_ValueError,
+ "Value of options dict should be an int.");
+ return -1;
+ }
+
+ /* Set parameter to compression context */
+ Py_BEGIN_CRITICAL_SECTION(self);
+ zstd_ret = ZSTD_DCtx_setParameter(self->dctx, key_v, value_v);
+ Py_END_CRITICAL_SECTION();
+
+ /* Check error */
+ if (ZSTD_isError(zstd_ret)) {
+ set_parameter_error(mod_state, 0, key_v, value_v);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+/* Load dictionary or prefix to decompression context */
+int
+_PyZstd_load_d_dict(ZstdDecompressor *self, PyObject *dict)
+{
+ size_t zstd_ret;
+ _zstd_state* const mod_state = PyType_GetModuleState(Py_TYPE(self));
+ if (mod_state == NULL) {
+ return -1;
+ }
+ ZstdDict *zd;
+ int type, ret;
+
+ /* Check ZstdDict */
+ ret = PyObject_IsInstance(dict, (PyObject*)mod_state->ZstdDict_type);
+ if (ret < 0) {
+ return -1;
+ }
+ else if (ret > 0) {
+ /* When decompressing, use digested dictionary by default. */
+ zd = (ZstdDict*)dict;
+ type = DICT_TYPE_DIGESTED;
+ goto load;
+ }
+
+ /* Check (ZstdDict, type) */
+ if (PyTuple_CheckExact(dict) && PyTuple_GET_SIZE(dict) == 2) {
+ /* Check ZstdDict */
+ ret = PyObject_IsInstance(PyTuple_GET_ITEM(dict, 0),
+ (PyObject*)mod_state->ZstdDict_type);
+ if (ret < 0) {
+ return -1;
+ }
+ else if (ret > 0) {
+ /* type == -1 may indicate an error. */
+ type = PyLong_AsInt(PyTuple_GET_ITEM(dict, 1));
+ if (type == DICT_TYPE_DIGESTED ||
+ type == DICT_TYPE_UNDIGESTED ||
+ type == DICT_TYPE_PREFIX)
+ {
+ assert(type >= 0);
+ zd = (ZstdDict*)PyTuple_GET_ITEM(dict, 0);
+ goto load;
+ }
+ }
+ }
+
+ /* Wrong type */
+ PyErr_SetString(PyExc_TypeError,
+ "zstd_dict argument should be ZstdDict object.");
+ return -1;
+
+load:
+ if (type == DICT_TYPE_DIGESTED) {
+ /* Get ZSTD_DDict */
+ ZSTD_DDict *d_dict = _get_DDict(zd);
+ if (d_dict == NULL) {
+ return -1;
+ }
+ /* Reference a prepared dictionary */
+ Py_BEGIN_CRITICAL_SECTION(self);
+ zstd_ret = ZSTD_DCtx_refDDict(self->dctx, d_dict);
+ Py_END_CRITICAL_SECTION();
+ }
+ else if (type == DICT_TYPE_UNDIGESTED) {
+ /* Load a dictionary */
+ Py_BEGIN_CRITICAL_SECTION2(self, zd);
+ zstd_ret = ZSTD_DCtx_loadDictionary(
+ self->dctx,
+ PyBytes_AS_STRING(zd->dict_content),
+ Py_SIZE(zd->dict_content));
+ Py_END_CRITICAL_SECTION2();
+ }
+ else if (type == DICT_TYPE_PREFIX) {
+ /* Load a prefix */
+ Py_BEGIN_CRITICAL_SECTION2(self, zd);
+ zstd_ret = ZSTD_DCtx_refPrefix(
+ self->dctx,
+ PyBytes_AS_STRING(zd->dict_content),
+ Py_SIZE(zd->dict_content));
+ Py_END_CRITICAL_SECTION2();
+ }
+ else {
+ /* Impossible code path */
+ PyErr_SetString(PyExc_SystemError,
+ "load_d_dict() impossible code path");
+ return -1;
+ }
+
+ /* Check error */
+ if (ZSTD_isError(zstd_ret)) {
+ set_zstd_error(mod_state, ERR_LOAD_D_DICT, zstd_ret);
+ return -1;
+ }
+ return 0;
+}
+
+
+
+/*
+ Given the two types of decompressors (defined in _zstdmodule.h):
+
+ typedef enum {
+ TYPE_DECOMPRESSOR, // <D>, ZstdDecompressor class
+ TYPE_ENDLESS_DECOMPRESSOR, // <E>, decompress() function
+ } decompress_type;
+
+ Decompress implementation for <D>, <E>, pseudo code:
+
+ initialize_output_buffer
+ while True:
+ decompress_data
+ set_object_flag # .eof for <D>, .at_frame_edge for <E>.
+
+ if output_buffer_exhausted:
+ if output_buffer_reached_max_length:
+ finish
+ grow_output_buffer
+ elif input_buffer_exhausted:
+ finish
+
+ ZSTD_decompressStream()'s size_t return value:
+ - 0 when a frame is completely decoded and fully flushed, zstd's internal
+ buffer has no data.
+ - An error code, which can be tested using ZSTD_isError().
+ - Or any other value > 0, which means there is still some decoding or
+ flushing to do to complete current frame.
+
+ Note, decompressing "an empty input" in any case will make it > 0.
+
+ <E> supports multiple frames, has an .at_frame_edge flag, it means both the
+ input and output streams are at a frame edge. The flag can be set by this
+ statement:
+
+ .at_frame_edge = (zstd_ret == 0) ? 1 : 0
+
+ But if decompressing "an empty input" at "a frame edge", zstd_ret will be
+ non-zero, then .at_frame_edge will be wrongly set to false. To solve this
+ problem, two AFE checks are needed to ensure that: when at "a frame edge",
+ empty input will not be decompressed.
+
+ // AFE check
+ if (self->at_frame_edge && in->pos == in->size) {
+ finish
+ }
+
+ In <E>, if .at_frame_edge is eventually set to true, but input stream has
+ unconsumed data (in->pos < in->size), then the outer function
+ stream_decompress() will set .at_frame_edge to false. In this case,
+ although the output stream is at a frame edge, for the caller, the input
+ stream is not at a frame edge, see below diagram. This behavior does not
+ affect the next AFE check, since (in->pos < in->size).
+
+ input stream: --------------|---
+ ^
+ output stream: ====================|
+ ^
+*/
+PyObject *
+decompress_impl(ZstdDecompressor *self, ZSTD_inBuffer *in,
+ Py_ssize_t max_length,
+ Py_ssize_t initial_size,
+ decompress_type type)
+{
+ size_t zstd_ret;
+ ZSTD_outBuffer out;
+ _BlocksOutputBuffer buffer = {.list = NULL};
+ PyObject *ret;
+
+ /* The first AFE check for setting .at_frame_edge flag */
+ if (type == TYPE_ENDLESS_DECOMPRESSOR) {
+ if (self->at_frame_edge && in->pos == in->size) {
+ _zstd_state* const mod_state = PyType_GetModuleState(Py_TYPE(self));
+ if (mod_state == NULL) {
+ return NULL;
+ }
+ ret = mod_state->empty_bytes;
+ Py_INCREF(ret);
+ return ret;
+ }
+ }
+
+ /* Initialize the output buffer */
+ if (initial_size >= 0) {
+ if (_OutputBuffer_InitWithSize(&buffer, &out, max_length, initial_size) < 0) {
+ goto error;
+ }
+ }
+ else {
+ if (_OutputBuffer_InitAndGrow(&buffer, &out, max_length) < 0) {
+ goto error;
+ }
+ }
+ assert(out.pos == 0);
+
+ while (1) {
+ /* Decompress */
+ Py_BEGIN_ALLOW_THREADS
+ zstd_ret = ZSTD_decompressStream(self->dctx, &out, in);
+ Py_END_ALLOW_THREADS
+
+ /* Check error */
+ if (ZSTD_isError(zstd_ret)) {
+ _zstd_state* const mod_state = PyType_GetModuleState(Py_TYPE(self));
+ if (mod_state != NULL) {
+ set_zstd_error(mod_state, ERR_DECOMPRESS, zstd_ret);
+ }
+ goto error;
+ }
+
+ /* Set .eof/.af_frame_edge flag */
+ if (type == TYPE_DECOMPRESSOR) {
+ /* ZstdDecompressor class stops when a frame is decompressed */
+ if (zstd_ret == 0) {
+ self->eof = 1;
+ break;
+ }
+ }
+ else if (type == TYPE_ENDLESS_DECOMPRESSOR) {
+ /* decompress() function supports multiple frames */
+ self->at_frame_edge = (zstd_ret == 0) ? 1 : 0;
+
+ /* The second AFE check for setting .at_frame_edge flag */
+ if (self->at_frame_edge && in->pos == in->size) {
+ break;
+ }
+ }
+
+ /* Need to check out before in. Maybe zstd's internal buffer still has
+ a few bytes can be output, grow the buffer and continue. */
+ if (out.pos == out.size) {
+ /* Output buffer exhausted */
+
+ /* Output buffer reached max_length */
+ if (_OutputBuffer_ReachedMaxLength(&buffer, &out)) {
+ break;
+ }
+
+ /* Grow output buffer */
+ if (_OutputBuffer_Grow(&buffer, &out) < 0) {
+ goto error;
+ }
+ assert(out.pos == 0);
+
+ }
+ else if (in->pos == in->size) {
+ /* Finished */
+ break;
+ }
+ }
+
+ /* Return a bytes object */
+ ret = _OutputBuffer_Finish(&buffer, &out);
+ if (ret != NULL) {
+ return ret;
+ }
+
+error:
+ _OutputBuffer_OnError(&buffer);
+ return NULL;
+}
+
+void
+decompressor_reset_session(ZstdDecompressor *self,
+ decompress_type type)
+{
+ // TODO(emmatyping): use _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED here
+ // and ensure lock is always held
+
+ /* Reset variables */
+ self->in_begin = 0;
+ self->in_end = 0;
+
+ if (type == TYPE_DECOMPRESSOR) {
+ Py_CLEAR(self->unused_data);
+ }
+
+ /* Reset variables in one operation */
+ self->needs_input = 1;
+ self->at_frame_edge = 1;
+ self->eof = 0;
+ self->_unused_char_for_align = 0;
+
+ /* Resetting session never fail */
+ ZSTD_DCtx_reset(self->dctx, ZSTD_reset_session_only);
+}
+
+PyObject *
+stream_decompress(ZstdDecompressor *self, Py_buffer *data, Py_ssize_t max_length,
+ decompress_type type)
+{
+ Py_ssize_t initial_buffer_size = -1;
+ ZSTD_inBuffer in;
+ PyObject *ret = NULL;
+ int use_input_buffer;
+
+ if (type == TYPE_DECOMPRESSOR) {
+ /* Check .eof flag */
+ if (self->eof) {
+ PyErr_SetString(PyExc_EOFError, "Already at the end of a zstd frame.");
+ assert(ret == NULL);
+ goto success;
+ }
+ }
+ else if (type == TYPE_ENDLESS_DECOMPRESSOR) {
+ /* Fast path for the first frame */
+ if (self->at_frame_edge && self->in_begin == self->in_end) {
+ /* Read decompressed size */
+ uint64_t decompressed_size = ZSTD_getFrameContentSize(data->buf, data->len);
+
+ /* These two zstd constants always > PY_SSIZE_T_MAX:
+ ZSTD_CONTENTSIZE_UNKNOWN is (0ULL - 1)
+ ZSTD_CONTENTSIZE_ERROR is (0ULL - 2)
+
+ Use ZSTD_findFrameCompressedSize() to check complete frame,
+ prevent allocating too much memory for small input chunk. */
+
+ if (decompressed_size <= (uint64_t) PY_SSIZE_T_MAX &&
+ !ZSTD_isError(ZSTD_findFrameCompressedSize(data->buf, data->len)) )
+ {
+ initial_buffer_size = (Py_ssize_t) decompressed_size;
+ }
+ }
+ }
+
+ /* Prepare input buffer w/wo unconsumed data */
+ if (self->in_begin == self->in_end) {
+ /* No unconsumed data */
+ use_input_buffer = 0;
+
+ in.src = data->buf;
+ in.size = data->len;
+ in.pos = 0;
+ }
+ else if (data->len == 0) {
+ /* Has unconsumed data, fast path for b'' */
+ assert(self->in_begin < self->in_end);
+
+ use_input_buffer = 1;
+
+ in.src = self->input_buffer + self->in_begin;
+ in.size = self->in_end - self->in_begin;
+ in.pos = 0;
+ }
+ else {
+ /* Has unconsumed data */
+ use_input_buffer = 1;
+
+ /* Unconsumed data size in input_buffer */
+ size_t used_now = self->in_end - self->in_begin;
+ assert(self->in_end > self->in_begin);
+
+ /* Number of bytes we can append to input buffer */
+ size_t avail_now = self->input_buffer_size - self->in_end;
+ assert(self->input_buffer_size >= self->in_end);
+
+ /* Number of bytes we can append if we move existing contents to
+ beginning of buffer */
+ size_t avail_total = self->input_buffer_size - used_now;
+ assert(self->input_buffer_size >= used_now);
+
+ if (avail_total < (size_t) data->len) {
+ char *tmp;
+ size_t new_size = used_now + data->len;
+
+ /* Allocate with new size */
+ tmp = PyMem_Malloc(new_size);
+ if (tmp == NULL) {
+ PyErr_NoMemory();
+ goto error;
+ }
+
+ /* Copy unconsumed data to the beginning of new buffer */
+ memcpy(tmp,
+ self->input_buffer + self->in_begin,
+ used_now);
+
+ /* Switch to new buffer */
+ PyMem_Free(self->input_buffer);
+ self->input_buffer = tmp;
+ self->input_buffer_size = new_size;
+
+ /* Set begin & end position */
+ self->in_begin = 0;
+ self->in_end = used_now;
+ }
+ else if (avail_now < (size_t) data->len) {
+ /* Move unconsumed data to the beginning.
+ Overlap is possible, so use memmove(). */
+ memmove(self->input_buffer,
+ self->input_buffer + self->in_begin,
+ used_now);
+
+ /* Set begin & end position */
+ self->in_begin = 0;
+ self->in_end = used_now;
+ }
+
+ /* Copy data to input buffer */
+ memcpy(self->input_buffer + self->in_end, data->buf, data->len);
+ self->in_end += data->len;
+
+ in.src = self->input_buffer + self->in_begin;
+ in.size = used_now + data->len;
+ in.pos = 0;
+ }
+ assert(in.pos == 0);
+
+ /* Decompress */
+ ret = decompress_impl(self, &in,
+ max_length, initial_buffer_size,
+ type);
+ if (ret == NULL) {
+ goto error;
+ }
+
+ /* Unconsumed input data */
+ if (in.pos == in.size) {
+ if (type == TYPE_DECOMPRESSOR) {
+ if (Py_SIZE(ret) == max_length || self->eof) {
+ self->needs_input = 0;
+ }
+ else {
+ self->needs_input = 1;
+ }
+ }
+ else if (type == TYPE_ENDLESS_DECOMPRESSOR) {
+ if (Py_SIZE(ret) == max_length && !self->at_frame_edge) {
+ self->needs_input = 0;
+ }
+ else {
+ self->needs_input = 1;
+ }
+ }
+
+ if (use_input_buffer) {
+ /* Clear input_buffer */
+ self->in_begin = 0;
+ self->in_end = 0;
+ }
+ }
+ else {
+ size_t data_size = in.size - in.pos;
+
+ self->needs_input = 0;
+
+ if (type == TYPE_ENDLESS_DECOMPRESSOR) {
+ self->at_frame_edge = 0;
+ }
+
+ if (!use_input_buffer) {
+ /* Discard buffer if it's too small
+ (resizing it may needlessly copy the current contents) */
+ if (self->input_buffer != NULL &&
+ self->input_buffer_size < data_size)
+ {
+ PyMem_Free(self->input_buffer);
+ self->input_buffer = NULL;
+ self->input_buffer_size = 0;
+ }
+
+ /* Allocate if necessary */
+ if (self->input_buffer == NULL) {
+ self->input_buffer = PyMem_Malloc(data_size);
+ if (self->input_buffer == NULL) {
+ PyErr_NoMemory();
+ goto error;
+ }
+ self->input_buffer_size = data_size;
+ }
+
+ /* Copy unconsumed data */
+ memcpy(self->input_buffer, (char*)in.src + in.pos, data_size);
+ self->in_begin = 0;
+ self->in_end = data_size;
+ }
+ else {
+ /* Use input buffer */
+ self->in_begin += in.pos;
+ }
+ }
+
+ goto success;
+
+error:
+ /* Reset decompressor's states/session */
+ decompressor_reset_session(self, type);
+
+ Py_CLEAR(ret);
+success:
+
+ return ret;
+}
+
+
+static PyObject *
+_zstd_ZstdDecompressor_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+ ZstdDecompressor *self;
+ self = PyObject_GC_New(ZstdDecompressor, type);
+ if (self == NULL) {
+ goto error;
+ }
+
+ self->inited = 0;
+ self->dict = NULL;
+ self->input_buffer = NULL;
+ self->input_buffer_size = 0;
+ self->in_begin = -1;
+ self->in_end = -1;
+ self->unused_data = NULL;
+ self->eof = 0;
+
+ /* needs_input flag */
+ self->needs_input = 1;
+
+ /* at_frame_edge flag */
+ self->at_frame_edge = 1;
+
+ /* Decompression context */
+ self->dctx = ZSTD_createDCtx();
+ if (self->dctx == NULL) {
+ _zstd_state* const mod_state = PyType_GetModuleState(Py_TYPE(self));
+ if (mod_state != NULL) {
+ PyErr_SetString(mod_state->ZstdError,
+ "Unable to create ZSTD_DCtx instance.");
+ }
+ goto error;
+ }
+
+ return (PyObject*)self;
+
+error:
+ if (self != NULL) {
+ PyObject_GC_Del(self);
+ }
+ return NULL;
+}
+
+static void
+ZstdDecompressor_dealloc(PyObject *ob)
+{
+ ZstdDecompressor *self = ZstdDecompressor_CAST(ob);
+
+ PyObject_GC_UnTrack(self);
+
+ /* Free decompression context */
+ ZSTD_freeDCtx(self->dctx);
+
+ /* Py_CLEAR the dict after free decompression context */
+ Py_CLEAR(self->dict);
+
+ /* Free unconsumed input data buffer */
+ PyMem_Free(self->input_buffer);
+
+ /* Free unused data */
+ Py_CLEAR(self->unused_data);
+
+ PyTypeObject *tp = Py_TYPE(self);
+ PyObject_GC_Del(ob);
+ Py_DECREF(tp);
+}
+
+/*[clinic input]
+_zstd.ZstdDecompressor.__init__
+
+ zstd_dict: object = None
+ A ZstdDict object, a pre-trained zstd dictionary.
+ options: object = None
+ A dict object that contains advanced decompression parameters.
+
+Create a decompressor object for decompressing data incrementally.
+
+Thread-safe at method level. For one-shot decompression, use the decompress()
+function instead.
+[clinic start generated code]*/
+
+static int
+_zstd_ZstdDecompressor___init___impl(ZstdDecompressor *self,
+ PyObject *zstd_dict, PyObject *options)
+/*[clinic end generated code: output=703af2f1ec226642 input=8fd72999acc1a146]*/
+{
+ /* Only called once */
+ if (self->inited) {
+ PyErr_SetString(PyExc_RuntimeError, init_twice_msg);
+ return -1;
+ }
+ self->inited = 1;
+
+ /* Load dictionary to decompression context */
+ if (zstd_dict != Py_None) {
+ if (_PyZstd_load_d_dict(self, zstd_dict) < 0) {
+ return -1;
+ }
+
+ /* Py_INCREF the dict */
+ Py_INCREF(zstd_dict);
+ self->dict = zstd_dict;
+ }
+
+ /* Set option to decompression context */
+ if (options != Py_None) {
+ if (_PyZstd_set_d_parameters(self, options) < 0) {
+ return -1;
+ }
+ }
+
+ // We can only start tracking self with the GC once self->dict is set.
+ PyObject_GC_Track(self);
+ return 0;
+}
+
+/*[clinic input]
+@critical_section
+@getter
+_zstd.ZstdDecompressor.unused_data
+
+A bytes object of un-consumed input data.
+
+When ZstdDecompressor object stops after a frame is
+decompressed, unused input data after the frame. Otherwise this will be b''.
+[clinic start generated code]*/
+
+static PyObject *
+_zstd_ZstdDecompressor_unused_data_get_impl(ZstdDecompressor *self)
+/*[clinic end generated code: output=f3a20940f11b6b09 input=5233800bef00df04]*/
+{
+ PyObject *ret;
+
+ /* Thread-safe code */
+ Py_BEGIN_CRITICAL_SECTION(self);
+
+ if (!self->eof) {
+ _zstd_state* const mod_state = PyType_GetModuleState(Py_TYPE(self));
+ if (mod_state == NULL) {
+ return NULL;
+ }
+ ret = mod_state->empty_bytes;
+ Py_INCREF(ret);
+ }
+ else {
+ if (self->unused_data == NULL) {
+ self->unused_data = PyBytes_FromStringAndSize(
+ self->input_buffer + self->in_begin,
+ self->in_end - self->in_begin);
+ ret = self->unused_data;
+ Py_XINCREF(ret);
+ }
+ else {
+ ret = self->unused_data;
+ Py_INCREF(ret);
+ }
+ }
+
+ Py_END_CRITICAL_SECTION();
+
+ return ret;
+}
+
+/*[clinic input]
+_zstd.ZstdDecompressor.decompress
+
+ data: Py_buffer
+ A bytes-like object, zstd data to be decompressed.
+ max_length: Py_ssize_t = -1
+ Maximum size of returned data. When it is negative, the size of
+ output buffer is unlimited. When it is nonnegative, returns at
+ most max_length bytes of decompressed data.
+
+Decompress *data*, returning uncompressed bytes if possible, or b'' otherwise.
+
+If *max_length* is nonnegative, returns at most *max_length* bytes of
+decompressed data. If this limit is reached and further output can be
+produced, *self.needs_input* will be set to ``False``. In this case, the next
+call to *decompress()* may provide *data* as b'' to obtain more of the output.
+
+If all of the input data was decompressed and returned (either because this
+was less than *max_length* bytes, or because *max_length* was negative),
+*self.needs_input* will be set to True.
+
+Attempting to decompress data after the end of a frame is reached raises an
+EOFError. Any data found after the end of the frame is ignored and saved in
+the self.unused_data attribute.
+[clinic start generated code]*/
+
+static PyObject *
+_zstd_ZstdDecompressor_decompress_impl(ZstdDecompressor *self,
+ Py_buffer *data,
+ Py_ssize_t max_length)
+/*[clinic end generated code: output=a4302b3c940dbec6 input=830e455bc9a50b6e]*/
+{
+ PyObject *ret;
+ /* Thread-safe code */
+ Py_BEGIN_CRITICAL_SECTION(self);
+
+ ret = stream_decompress(self, data, max_length, TYPE_DECOMPRESSOR);
+ Py_END_CRITICAL_SECTION();
+ return ret;
+}
+
+#define clinic_state() (get_zstd_state_from_type(type))
+#include "clinic/decompressor.c.h"
+#undef clinic_state
+
+static PyMethodDef ZstdDecompressor_methods[] = {
+ _ZSTD_ZSTDDECOMPRESSOR_DECOMPRESS_METHODDEF
+
+ {0}
+};
+
+PyDoc_STRVAR(ZstdDecompressor_eof_doc,
+"True means the end of the first frame has been reached. If decompress data\n"
+"after that, an EOFError exception will be raised.");
+
+PyDoc_STRVAR(ZstdDecompressor_needs_input_doc,
+"If the max_length output limit in .decompress() method has been reached, and\n"
+"the decompressor has (or may has) unconsumed input data, it will be set to\n"
+"False. In this case, pass b'' to .decompress() method may output further data.");
+
+static PyMemberDef ZstdDecompressor_members[] = {
+ {"eof", Py_T_BOOL, offsetof(ZstdDecompressor, eof),
+ Py_READONLY, ZstdDecompressor_eof_doc},
+
+ {"needs_input", Py_T_BOOL, offsetof(ZstdDecompressor, needs_input),
+ Py_READONLY, ZstdDecompressor_needs_input_doc},
+
+ {0}
+};
+
+static PyGetSetDef ZstdDecompressor_getset[] = {
+ _ZSTD_ZSTDDECOMPRESSOR_UNUSED_DATA_GETSETDEF
+
+ {0}
+};
+
+static int
+ZstdDecompressor_traverse(PyObject *ob, visitproc visit, void *arg)
+{
+ ZstdDecompressor *self = ZstdDecompressor_CAST(ob);
+ Py_VISIT(self->dict);
+ return 0;
+}
+
+static int
+ZstdDecompressor_clear(PyObject *ob)
+{
+ ZstdDecompressor *self = ZstdDecompressor_CAST(ob);
+ Py_CLEAR(self->dict);
+ Py_CLEAR(self->unused_data);
+ return 0;
+}
+
+static PyType_Slot ZstdDecompressor_slots[] = {
+ {Py_tp_new, _zstd_ZstdDecompressor_new},
+ {Py_tp_dealloc, ZstdDecompressor_dealloc},
+ {Py_tp_init, _zstd_ZstdDecompressor___init__},
+ {Py_tp_methods, ZstdDecompressor_methods},
+ {Py_tp_members, ZstdDecompressor_members},
+ {Py_tp_getset, ZstdDecompressor_getset},
+ {Py_tp_doc, (char*)_zstd_ZstdDecompressor___init____doc__},
+ {Py_tp_traverse, ZstdDecompressor_traverse},
+ {Py_tp_clear, ZstdDecompressor_clear},
+ {0}
+};
+
+PyType_Spec ZstdDecompressor_type_spec = {
+ .name = "_zstd.ZstdDecompressor",
+ .basicsize = sizeof(ZstdDecompressor),
+ .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
+ .slots = ZstdDecompressor_slots,
+};