@@ -40,12 +40,44 @@ public static byte[] compress(byte[] src) {
4040 /// @return a self-describing zstd frame
4141 public static byte [] compress (byte [] src , int level ) {
4242 Objects .requireNonNull (src , "src" );
43+ return compress (src , 0 , src .length , level );
44+ }
45+
46+ /// Compresses the `length`-byte sub-range of `src` starting at `offset`, at the
47+ /// library default level. Lets a caller holding a payload inside a larger buffer
48+ /// compress it without copying the sub-range out first.
49+ ///
50+ /// @param src buffer holding the bytes to compress
51+ /// @param offset index of the first byte to compress
52+ /// @param length number of bytes to compress
53+ /// @return a self-describing zstd frame
54+ /// @throws IndexOutOfBoundsException if `offset` and `length` do not denote a
55+ /// valid range within `src`
56+ public static byte [] compress (byte [] src , int offset , int length ) {
57+ return compress (src , offset , length , defaultCompressionLevel ());
58+ }
59+
60+ /// Compresses the `length`-byte sub-range of `src` starting at `offset`, at the
61+ /// given level. Lets a caller holding a payload inside a larger buffer compress
62+ /// it without copying the sub-range out first.
63+ ///
64+ /// @param src buffer holding the bytes to compress
65+ /// @param offset index of the first byte to compress
66+ /// @param length number of bytes to compress
67+ /// @param level compression level in [[#minCompressionLevel()], [#maxCompressionLevel()]];
68+ /// higher is smaller but slower
69+ /// @return a self-describing zstd frame
70+ /// @throws IndexOutOfBoundsException if `offset` and `length` do not denote a
71+ /// valid range within `src`
72+ public static byte [] compress (byte [] src , int offset , int length , int level ) {
73+ Objects .requireNonNull (src , "src" );
74+ Objects .checkFromIndexSize (offset , length , src .length );
4375 try (Arena arena = Arena .ofConfined ()) {
44- MemorySegment in = copyIn (arena , src );
45- long bound = compressBound (src . length );
76+ MemorySegment in = copyIn (arena , src , offset , length );
77+ long bound = compressBound (length );
4678 MemorySegment out = arena .allocate (bound );
4779 long written = NativeCall .checkReturnValue (() -> (long ) Bindings .COMPRESS .invokeExact (
48- out , bound , in , (long ) src . length , level ));
80+ out , bound , in , (long ) length , level ));
4981 return copyOut (out , written );
5082 }
5183 }
@@ -101,11 +133,33 @@ private static int toArrayLength(long size) {
101133 /// @throws ZstdException if the frame is invalid or larger than `maxSize`
102134 public static byte [] decompress (byte [] compressed , int maxSize ) {
103135 Objects .requireNonNull (compressed , "compressed" );
136+ return decompress (compressed , 0 , compressed .length , maxSize );
137+ }
138+
139+ /// Decompresses the `length`-byte sub-range of `compressed` starting at `offset`
140+ /// into a buffer of at most `maxSize` bytes. Lets a caller decode a frame embedded
141+ /// inside a larger buffer without copying the frame out first; otherwise identical
142+ /// to [#decompress(byte[], int)].
143+ ///
144+ /// As with [#decompress(byte[], int)], `maxSize` caps the allocation and decode,
145+ /// so this is the safe entry point for **untrusted** input.
146+ ///
147+ /// @param compressed buffer holding a complete zstd frame
148+ /// @param offset index of the first byte of the frame
149+ /// @param length number of bytes the frame occupies
150+ /// @param maxSize upper bound on the decompressed length
151+ /// @return the original bytes (length ≤ `maxSize`)
152+ /// @throws IndexOutOfBoundsException if `offset` and `length` do not denote a
153+ /// valid range within `compressed`
154+ /// @throws ZstdException if the frame is invalid or larger than `maxSize`
155+ public static byte [] decompress (byte [] compressed , int offset , int length , int maxSize ) {
156+ Objects .requireNonNull (compressed , "compressed" );
157+ Objects .checkFromIndexSize (offset , length , compressed .length );
104158 try (Arena arena = Arena .ofConfined ()) {
105- MemorySegment in = copyIn (arena , compressed );
159+ MemorySegment in = copyIn (arena , compressed , offset , length );
106160 MemorySegment out = arena .allocate (Math .max (maxSize , 1 ));
107161 long written = NativeCall .checkReturnValue (() -> (long ) Bindings .DECOMPRESS .invokeExact (
108- out , (long ) maxSize , in , (long ) compressed . length ));
162+ out , (long ) maxSize , in , (long ) length ));
109163 return copyOut (out , written );
110164 }
111165 }
@@ -316,8 +370,12 @@ public static int versionNumber() {
316370 // Native-call status checking and segment guards live in NativeCall.
317371
318372 static MemorySegment copyIn (Arena arena , byte [] src ) {
319- MemorySegment seg = arena .allocate (Math .max (src .length , 1 ));
320- MemorySegment .copy (src , 0 , seg , JAVA_BYTE , 0 , src .length );
373+ return copyIn (arena , src , 0 , src .length );
374+ }
375+
376+ static MemorySegment copyIn (Arena arena , byte [] src , int offset , int length ) {
377+ MemorySegment seg = arena .allocate (Math .max (length , 1 ));
378+ MemorySegment .copy (src , offset , seg , JAVA_BYTE , 0 , length );
321379 return seg ;
322380 }
323381
0 commit comments