{"record":{"id":"e10941eac70d07e5","repo":"elastic/elasticsearch","slug":"zstdlib-geterrorname-ret","errorCode":null,"errorMessage":"{zstdLib.getErrorName(ret)}","messagePattern":"\\{zstdLib\\.getErrorName\\(ret\\)\\}","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"libs/native/src/main/java/org/elasticsearch/nativeaccess/Zstd.java","lineNumber":77,"sourceCode":"            throw new IllegalArgumentException(zstdLib.getErrorName(ret));\n        } else if (ret < 0 || ret > Integer.MAX_VALUE) {\n            throw new IllegalStateException(\"Integer overflow? ret=\" + ret);\n        }\n        return (int) ret;\n    }\n\n    /**\n     * Decompress the content of {@code src} into {@code dst}, and return the number of decompressed bytes. {@link ByteBuffer#position()}\n     * and {@link ByteBuffer#limit()} of both {@link ByteBuffer}s are left unmodified.\n     */\n    public int decompress(CloseableByteBuffer dst, CloseableByteBuffer src) {\n        Objects.requireNonNull(dst, \"Null destination buffer\");\n        Objects.requireNonNull(src, \"Null source buffer\");\n        long dstSize = dst.buffer().remaining();\n        long srcSize = src.buffer().remaining();\n        long ret = zstdLib.decompress(MemorySegment.ofBuffer(dst.buffer()), dstSize, MemorySegment.ofBuffer(src.buffer()), srcSize);\n        if (zstdLib.isError(ret)) {\n            throw new IllegalArgumentException(zstdLib.getErrorName(ret));\n        } else if (ret < 0 || ret > Integer.MAX_VALUE) {\n            throw new IllegalStateException(\"Integer overflow? ret=\" + ret);\n        }\n        return (int) ret;\n    }\n\n    /**\n     * Decompress the content of {@code src} into {@code dst}, and return the number of decompressed bytes.\n     * Both segments may be native or heap-backed. On JDK 22+ heap segments are passed through the\n     * critical downcall without copying; on JDK 21 the fallback adapter stages them via a confined arena.\n     */\n    public int decompress(MemorySegment dst, MemorySegment src) {\n        Objects.requireNonNull(dst, \"Null dst segment\");\n        Objects.requireNonNull(src, \"Null src segment\");\n        long ret = zstdLib.decompressHeap(dst, dst.byteSize(), src, src.byteSize());\n        if (zstdLib.isError(ret)) {\n            throw new IllegalArgumentException(zstdLib.getErrorName(ret));\n        } else if (ret < 0 || ret > Integer.MAX_VALUE) {","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/elastic/elasticsearch/blob/db6a809a667c081ca1dc7500389d26975573215f/libs/native/src/main/java/org/elasticsearch/nativeaccess/Zstd.java#L59-L95","documentation":"Thrown as IllegalArgumentException when libzstd's ZSTD_decompress returns an error code (detected via ZSTD_isError). The message is the human-readable error name from ZSTD_getErrorName(ret). Common libzstd errors include corrupted frame magic, truncated input, destination buffer too small, or checksum mismatch. This indicates the compressed data is invalid or the output buffer is undersized.","triggerScenarios":"Calling Zstd.decompress(dst, src) where src does not contain a valid complete zstd frame, or dst.remaining() is smaller than the decompressed content size. Also triggered by partial reads where only a prefix of the compressed frame was loaded into src.","commonSituations":"Reading a corrupted or truncated Lucene segment compressed with zstd. Passing a ByteBuffer whose position/limit slice does not cover the full compressed frame. Mixing zstd frame versions. Disk I/O errors causing partial reads. Using a dst buffer sized for the compressed rather than decompressed size.","solutions":["Verify the source buffer starts with the zstd magic number (0x28B52FFD) and spans a complete frame.","Ensure dst.remaining() >= the original uncompressed size (use the frame's content size header or compressBound).","If reading from a stream/channel, verify all compressed bytes were read before decompressing.","Check for disk corruption: re-read or restore the compressed data from a healthy replica.","Log the exact ZSTD_getErrorName string to distinguish corruption from buffer-size issues."],"exampleFix":"// before: dst buffer too small\nByteBuffer dst = ByteBuffer.allocate(src.remaining()); // sized for compressed, not decompressed\nzstd.decompress(wrap(dst), wrap(src));\n\n// after: dst sized to decompressed content size\nlong decompressedSize = Zstd.decompressedSize(src); // read frame header\nByteBuffer dst = ByteBuffer.allocate((int) decompressedSize);\nzstd.decompress(wrap(dst), wrap(src));","handlingStrategy":"validation","validationCode":"// Verify zstd magic bytes and minimum frame size before decompressing\nint magic = src.getInt(src.position()) & 0xFFFFFFFF;\nif (magic != 0xFD2FB528) {\n    throw new IllegalArgumentException(\"Source buffer does not start with zstd magic number\");\n}\nif (dst.remaining() < minRequiredDecompressedSize) {\n    throw new IllegalArgumentException(\"Destination buffer too small for decompressed data\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    int decompressed = zstd.decompress(dst, src);\n} catch (IllegalArgumentException e) {\n    logger.error(\"Zstd decompression failed: {}\", e.getMessage());\n    // trigger segment recovery or fallback read\n    throw new IOException(\"Corrupted zstd-compressed data\", e);\n}","preventionTips":["Always size the destination buffer from the frame's declared content size, not the compressed size.","Validate the zstd magic number before decompressing untrusted input.","Ensure complete reads of compressed data before invoking decompress.","Monitor disk health to catch corruption before it reaches the decompressor."],"tags":["zstd","compression","native","data-integrity","decompression"],"analyzedSha":"db6a809a667c081ca1dc7500389d26975573215f","analyzedAt":"2026-08-12T01:39:14.192Z","schemaVersion":2},"datasetVersion":"2026-08-12T08:17:17.861Z"}