|
| 1 | +/*+***************************************************************************** |
| 2 | + * ___ _ ____ ____ |
| 3 | + * / _ \ _ _ ___ ___| |_| _ \| __ ) |
| 4 | + * | | | | | | |/ _ \/ __| __| | | | _ \ |
| 5 | + * | |_| | |_| | __/\__ \ |_| |_| | |_) | |
| 6 | + * \__\_\\__,_|\___||___/\__|____/|____/ |
| 7 | + * |
| 8 | + * Copyright (c) 2014-2019 Appsicle |
| 9 | + * Copyright (c) 2019-2026 QuestDB |
| 10 | + * |
| 11 | + * Licensed under the Apache License, Version 2.0 (the "License"); |
| 12 | + * you may not use this file except in compliance with the License. |
| 13 | + * You may obtain a copy of the License at |
| 14 | + * |
| 15 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 16 | + * |
| 17 | + * Unless required by applicable law or agreed to in writing, software |
| 18 | + * distributed under the License is distributed on an "AS IS" BASIS, |
| 19 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 20 | + * See the License for the specific language governing permissions and |
| 21 | + * limitations under the License. |
| 22 | + * |
| 23 | + ******************************************************************************/ |
| 24 | + |
| 25 | +/* |
| 26 | + * JNI wrapper over the bundled libzstd (decompression only). The server ships |
| 27 | + * compression support in the Rust qdbr crate; this file covers the client |
| 28 | + * decompression path so RESULT_BATCH frames with FLAG_ZSTD can be decoded |
| 29 | + * without any external native dependency. |
| 30 | + * |
| 31 | + * libzstd is vendored as a git submodule at share/zstd/ pinned to v1.5.7; |
| 32 | + * this file lives alongside (not inside) the submodule so upstream resets |
| 33 | + * don't nuke our JNI glue. |
| 34 | + */ |
| 35 | + |
| 36 | +#include <jni.h> |
| 37 | +#include <stdint.h> |
| 38 | +#include <stdlib.h> |
| 39 | +#include "zstd.h" |
| 40 | + |
| 41 | +JNIEXPORT jlong JNICALL Java_io_questdb_client_std_Zstd_createDCtx( |
| 42 | + JNIEnv *env, jclass cls) { |
| 43 | + return (jlong) (uintptr_t) ZSTD_createDCtx(); |
| 44 | +} |
| 45 | + |
| 46 | +JNIEXPORT jlong JNICALL Java_io_questdb_client_std_Zstd_getFrameContentSize( |
| 47 | + JNIEnv *env, jclass cls, |
| 48 | + jlong src_addr, jlong src_len) { |
| 49 | + /* |
| 50 | + * Peeks the zstd frame header at src_addr to recover the declared |
| 51 | + * uncompressed size. Returns: |
| 52 | + * positive -- declared content size in bytes |
| 53 | + * -1 -- frame valid, content size not stored (ZSTD_CONTENTSIZE_UNKNOWN) |
| 54 | + * -2 -- invalid frame, truncated header, or size > INT64_MAX |
| 55 | + * |
| 56 | + * Lets the Java caller size the destination buffer in a single allocation |
| 57 | + * instead of retrying decompress on dst-too-small. Crucially, it also lets |
| 58 | + * a corrupt frame fail BEFORE any output buffer growth, eliminating a |
| 59 | + * memory-amplification vector where one bad frame would have driven |
| 60 | + * scratch growth all the way to the 64 MiB cap. |
| 61 | + */ |
| 62 | + if (src_len < 0 || (src_len > 0 && src_addr == 0)) { |
| 63 | + return -2; |
| 64 | + } |
| 65 | + unsigned long long size = ZSTD_getFrameContentSize( |
| 66 | + (const void *) (uintptr_t) src_addr, (size_t) src_len); |
| 67 | + if (size == ZSTD_CONTENTSIZE_UNKNOWN) { |
| 68 | + return -1; |
| 69 | + } |
| 70 | + if (size == ZSTD_CONTENTSIZE_ERROR) { |
| 71 | + return -2; |
| 72 | + } |
| 73 | + if (size > (unsigned long long) INT64_MAX) { |
| 74 | + /* Cast to jlong would wrap to negative and look like an error code; |
| 75 | + * reject upfront so the caller doesn't double-interpret. */ |
| 76 | + return -2; |
| 77 | + } |
| 78 | + return (jlong) size; |
| 79 | +} |
| 80 | + |
| 81 | +JNIEXPORT void JNICALL Java_io_questdb_client_std_Zstd_freeDCtx( |
| 82 | + JNIEnv *env, jclass cls, jlong ptr) { |
| 83 | + if (ptr != 0) { |
| 84 | + ZSTD_freeDCtx((ZSTD_DCtx *) (uintptr_t) ptr); |
| 85 | + } |
| 86 | +} |
| 87 | + |
| 88 | +JNIEXPORT jlong JNICALL Java_io_questdb_client_std_Zstd_decompress( |
| 89 | + JNIEnv *env, jclass cls, |
| 90 | + jlong ctx, |
| 91 | + jlong src_addr, jlong src_len, |
| 92 | + jlong dst_addr, jlong dst_cap) { |
| 93 | + if (ctx == 0) { |
| 94 | + return -1; |
| 95 | + } |
| 96 | + ZSTD_DCtx *dctx = (ZSTD_DCtx *) (uintptr_t) ctx; |
| 97 | + size_t n = ZSTD_decompressDCtx( |
| 98 | + dctx, |
| 99 | + (void *) (uintptr_t) dst_addr, (size_t) dst_cap, |
| 100 | + (const void *) (uintptr_t) src_addr, (size_t) src_len); |
| 101 | + if (ZSTD_isError(n)) { |
| 102 | + unsigned code = ZSTD_getErrorCode(n); |
| 103 | + return -(jlong) code; |
| 104 | + } |
| 105 | + return (jlong) n; |
| 106 | +} |
0 commit comments