Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions zstd/src/main/java/io/github/dfa1/zstd/NativeCall.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.lang.foreign.MemorySegment;
import java.nio.charset.StandardCharsets;
import java.util.Objects;

/// Package-private helpers that adapt raw FFM downcalls to the zstd error
/// convention: run a native call, decode a zstd `size_t` error code into a
Expand Down Expand Up @@ -60,6 +61,7 @@ private static String errorName(long code) {
/// by native (off-heap) memory, since its address is dereferenced in C. Fails
/// fast with a clear message instead of the FFM linker's cryptic error.
static void requireNative(MemorySegment seg, String name) {
Objects.requireNonNull(seg, name);
if (!seg.isNative()) {
throw new IllegalArgumentException(
name + " must be a native (off-heap) MemorySegment; got a heap segment");
Expand Down
8 changes: 6 additions & 2 deletions zstd/src/main/java/io/github/dfa1/zstd/Zstd.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.nio.charset.StandardCharsets;
import java.util.Objects;

import static java.lang.foreign.ValueLayout.JAVA_BYTE;

Expand All @@ -19,9 +20,9 @@
public final class Zstd {

/// Sentinel returned by zstd when a frame carries no decompressed-size header.
private static final long CONTENTSIZE_UNKNOWN = -1L;
static final long CONTENTSIZE_UNKNOWN = -1L;
/// Sentinel returned by zstd when the input is not a valid zstd frame.
private static final long CONTENTSIZE_ERROR = -2L;
static final long CONTENTSIZE_ERROR = -2L;

/// Compresses `src` at the library default level.
///
Expand All @@ -38,6 +39,7 @@ public static byte[] compress(byte[] src) {
/// higher is smaller but slower
/// @return a self-describing zstd frame
public static byte[] compress(byte[] src, int level) {
Objects.requireNonNull(src, "src");
try (Arena arena = Arena.ofConfined()) {
MemorySegment in = copyIn(arena, src);
long bound = compressBound(src.length);
Expand All @@ -56,6 +58,7 @@ public static byte[] compress(byte[] src, int level) {
/// @throws ZstdException if the frame is invalid or its content size is not stored;
/// use {@link #decompress(byte[], int)} for the latter
public static byte[] decompress(byte[] compressed) {
Objects.requireNonNull(compressed, "compressed");
long size = frameContentSize(compressed);
if (size == CONTENTSIZE_UNKNOWN) {
throw new ZstdException("decompressed size not stored in frame; call decompress(src, maxSize)");
Expand All @@ -74,6 +77,7 @@ public static byte[] decompress(byte[] compressed) {
/// @return the original bytes (length ≤ `maxSize`)
/// @throws ZstdException if the frame is invalid or larger than `maxSize`
public static byte[] decompress(byte[] compressed, int maxSize) {
Objects.requireNonNull(compressed, "compressed");
try (Arena arena = Arena.ofConfined()) {
MemorySegment in = copyIn(arena, compressed);
MemorySegment out = arena.allocate(Math.max(maxSize, 1));
Expand Down
6 changes: 6 additions & 0 deletions zstd/src/main/java/io/github/dfa1/zstd/ZstdCompressCtx.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.util.Objects;

/// A reusable compression context.
///
Expand Down Expand Up @@ -95,6 +96,7 @@ public ZstdCompressCtx windowLog(int windowLog) {
/// @param src the bytes to compress
/// @return a self-describing zstd frame
public byte[] compress(byte[] src) {
Objects.requireNonNull(src, "src");
try (Arena arena = Arena.ofConfined()) {
MemorySegment in = Zstd.copyIn(arena, src);
long bound = Zstd.compressBound(src.length);
Expand All @@ -119,6 +121,8 @@ private void setParam(ZstdCompressParameter parameter, int value) {
/// @param dict the dictionary to compress against
/// @return a self-describing zstd frame
public byte[] compress(byte[] src, ZstdDictionary dict) {
Objects.requireNonNull(src, "src");
Objects.requireNonNull(dict, "dict");
try (Arena arena = Arena.ofConfined()) {
MemorySegment in = Zstd.copyIn(arena, src);
byte[] d = dict.raw();
Expand All @@ -138,6 +142,8 @@ public byte[] compress(byte[] src, ZstdDictionary dict) {
/// @param dict the pre-digested compression dictionary
/// @return a self-describing zstd frame
public byte[] compress(byte[] src, ZstdCompressDict dict) {
Objects.requireNonNull(src, "src");
Objects.requireNonNull(dict, "dict");
try (Arena arena = Arena.ofConfined()) {
MemorySegment in = Zstd.copyIn(arena, src);
long bound = Zstd.compressBound(src.length);
Expand Down
4 changes: 4 additions & 0 deletions zstd/src/main/java/io/github/dfa1/zstd/ZstdCompressDict.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.util.Objects;

/// A dictionary digested once for compression at a fixed level.
///
Expand All @@ -10,6 +11,8 @@
/// cost — the right choice when compressing many payloads against the same
/// dictionary. The raw {@link ZstdDictionary} bytes are copied into native
/// memory, so the source may be discarded afterwards.
///
/// Immutable once built and safe to share across threads (the digested dictionary is read-only).
public final class ZstdCompressDict extends NativeObject {

private final int level;
Expand Down Expand Up @@ -54,6 +57,7 @@ public ZstdCompressDict(MemorySegment dict) {
}

private static MemorySegment create(ZstdDictionary dict, int level) {
Objects.requireNonNull(dict, "dict");
try (Arena arena = Arena.ofConfined()) {
byte[] raw = dict.raw();
MemorySegment d = Zstd.copyIn(arena, raw);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
/// } while (!r.isComplete());
/// }
/// }
///
/// Not thread-safe: confine an instance to a single thread.
public final class ZstdCompressStream extends NativeObject {

private final Arena arena = Arena.ofConfined();
Expand Down
6 changes: 6 additions & 0 deletions zstd/src/main/java/io/github/dfa1/zstd/ZstdDecompressCtx.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.util.Objects;

/// A reusable decompression context.
///
Expand Down Expand Up @@ -52,6 +53,7 @@ public ZstdDecompressCtx windowLogMax(int windowLogMax) {
/// @param maxSize upper bound on the decompressed length
/// @return the original bytes
public byte[] decompress(byte[] compressed, int maxSize) {
Objects.requireNonNull(compressed, "compressed");
try (Arena arena = Arena.ofConfined()) {
MemorySegment in = Zstd.copyIn(arena, compressed);
MemorySegment out = arena.allocate(Math.max(maxSize, 1));
Expand All @@ -72,6 +74,8 @@ public byte[] decompress(byte[] compressed, int maxSize) {
/// @param dict the dictionary the frame was compressed against
/// @return the original bytes
public byte[] decompress(byte[] compressed, int maxSize, ZstdDictionary dict) {
Objects.requireNonNull(compressed, "compressed");
Objects.requireNonNull(dict, "dict");
try (Arena arena = Arena.ofConfined()) {
MemorySegment in = Zstd.copyIn(arena, compressed);
byte[] d = dict.raw();
Expand All @@ -90,6 +94,8 @@ public byte[] decompress(byte[] compressed, int maxSize, ZstdDictionary dict) {
/// @param dict the pre-digested decompression dictionary
/// @return the original bytes
public byte[] decompress(byte[] compressed, int maxSize, ZstdDecompressDict dict) {
Objects.requireNonNull(compressed, "compressed");
Objects.requireNonNull(dict, "dict");
try (Arena arena = Arena.ofConfined()) {
MemorySegment in = Zstd.copyIn(arena, compressed);
MemorySegment out = arena.allocate(Math.max(maxSize, 1));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@

import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.util.Objects;

/// A dictionary digested once for decompression.
///
/// Pre-processes the dictionary so each
/// {@link ZstdDecompressCtx#decompress(byte[], int, ZstdDecompressDict)} call
/// skips that cost. The raw {@link ZstdDictionary} bytes are copied into native
/// memory, so the source may be discarded afterwards.
///
/// Immutable once built and safe to share across threads (the digested dictionary is read-only).
public final class ZstdDecompressDict extends NativeObject {

/// Digests `dict` for decompression.
Expand All @@ -31,6 +34,7 @@ public ZstdDecompressDict(MemorySegment dict) {
}

private static MemorySegment create(ZstdDictionary dict) {
Objects.requireNonNull(dict, "dict");
try (Arena arena = Arena.ofConfined()) {
byte[] raw = dict.raw();
MemorySegment d = Zstd.copyIn(arena, raw);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
/// pipelines prefer [ZstdInputStream]. Feed compressed input, drain the
/// destination when it fills; a result is [ZstdStreamResult#isComplete()] when
/// the current frame is fully decoded.
///
/// Not thread-safe: confine an instance to a single thread.
public final class ZstdDecompressStream extends NativeObject {

private final Arena arena = Arena.ofConfined();
Expand Down
6 changes: 6 additions & 0 deletions zstd/src/main/java/io/github/dfa1/zstd/ZstdDictionary.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.lang.invoke.MethodHandle;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Objects;

import static java.lang.foreign.ValueLayout.JAVA_BYTE;
import static java.lang.foreign.ValueLayout.JAVA_DOUBLE;
Expand Down Expand Up @@ -79,6 +80,7 @@ private ZstdDictionary(byte[] bytes) {
/// @param raw dictionary bytes; defensively copied
/// @return a dictionary wrapping a copy of `raw`
public static ZstdDictionary of(byte[] raw) {
Objects.requireNonNull(raw, "raw");
return new ZstdDictionary(raw.clone());
}

Expand All @@ -92,6 +94,7 @@ public static ZstdDictionary of(byte[] raw) {
/// @return the trained dictionary
/// @throws ZstdException if training fails (commonly: not enough sample data)
public static ZstdDictionary train(List<byte[]> samples, int maxDictBytes) {
Objects.requireNonNull(samples, "samples");
if (samples.isEmpty()) {
throw new ZstdException("cannot train a dictionary from zero samples");
}
Expand Down Expand Up @@ -175,6 +178,7 @@ public static ZstdDictionary trainFastCover(List<byte[]> samples, int maxDictByt

private static ZstdDictionary optimize(List<byte[]> samples, int maxDictBytes,
int compressionLevel, boolean fast) {
Objects.requireNonNull(samples, "samples");
if (samples.isEmpty()) {
throw new ZstdException("cannot train a dictionary from zero samples");
}
Expand Down Expand Up @@ -228,6 +232,8 @@ private static ZstdDictionary optimize(List<byte[]> samples, int maxDictBytes,
/// @throws ZstdException if finalisation fails
public static ZstdDictionary finalizeFrom(byte[] content, List<byte[]> samples,
int maxDictBytes, int compressionLevel) {
Objects.requireNonNull(content, "content");
Objects.requireNonNull(samples, "samples");
if (samples.isEmpty()) {
throw new ZstdException("cannot finalise a dictionary from zero samples");
}
Expand Down
5 changes: 1 addition & 4 deletions zstd/src/main/java/io/github/dfa1/zstd/ZstdFrame.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@
/// data already off-heap (e.g. an mmap slice); see `docs/zero-copy.md`.
public final class ZstdFrame {

/// Sentinel returned by `ZSTD_decompressBound` when the input is not valid.
private static final long CONTENT_SIZE_ERROR = -2L;

/// Tests whether `data` begins with a valid zstd frame (standard or skippable).
///
/// @param data the bytes to inspect
Expand Down Expand Up @@ -213,7 +210,7 @@ private static long decompressedBound(MemorySegment data, long size) {
} catch (Throwable t) {
throw NativeCall.rethrow(t);
}
if (bound == CONTENT_SIZE_ERROR) {
if (bound == Zstd.CONTENTSIZE_ERROR) {
throw new ZstdException("not valid zstd data");
}
return bound;
Expand Down
5 changes: 1 addition & 4 deletions zstd/src/main/java/io/github/dfa1/zstd/ZstdFrameHeader.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,11 @@ public record ZstdFrameHeader(
int dictId,
boolean hasChecksum) {

/// Sentinel meaning the decompressed size is not recorded in the frame.
private static final long CONTENTSIZE_UNKNOWN = -1L;

/// The decompressed size, if the frame records it.
///
/// @return the content size, or empty if the frame does not store it
public OptionalLong contentSize() {
return frameContentSize == CONTENTSIZE_UNKNOWN
return frameContentSize == Zstd.CONTENTSIZE_UNKNOWN
? OptionalLong.empty()
: OptionalLong.of(frameContentSize);
}
Expand Down
5 changes: 4 additions & 1 deletion zstd/src/main/java/io/github/dfa1/zstd/ZstdInputStream.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.io.InputStream;
import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.util.Objects;

import static java.lang.foreign.ValueLayout.JAVA_BYTE;

Expand All @@ -22,6 +23,8 @@
/// zin.transferTo(sink);
/// }
/// }
///
/// Not thread-safe: confine an instance to a single thread.
public final class ZstdInputStream extends InputStream {

private final InputStream in;
Expand Down Expand Up @@ -55,7 +58,7 @@ public ZstdInputStream(InputStream in) {
/// @param in the stream to read the compressed frame from
/// @param dictionary the dictionary the frame was compressed against, or `null` for none
public ZstdInputStream(InputStream in, ZstdDictionary dictionary) {
this.in = in;
this.in = Objects.requireNonNull(in, "in");
MemorySegment d = null;
try {
d = (MemorySegment) Bindings.CREATE_DCTX.invokeExact();
Expand Down
5 changes: 4 additions & 1 deletion zstd/src/main/java/io/github/dfa1/zstd/ZstdOutputStream.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.io.OutputStream;
import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.util.Objects;

import static java.lang.foreign.ValueLayout.JAVA_BYTE;

Expand All @@ -22,6 +23,8 @@
/// source.transferTo(zout);
/// }
/// }
///
/// Not thread-safe: confine an instance to a single thread.
public final class ZstdOutputStream extends OutputStream {

// ZSTD_cParameter / ZSTD_EndDirective values from zstd.h — see
Expand Down Expand Up @@ -92,7 +95,7 @@ private void setPledgedSrcSize(long pledgedSrcSize) {
/// @param level the compression level
/// @param dictionary the dictionary to compress against, or `null` for none
public ZstdOutputStream(OutputStream out, int level, ZstdDictionary dictionary) {
this.out = out;
this.out = Objects.requireNonNull(out, "out");
MemorySegment c = null;
try {
c = (MemorySegment) Bindings.CREATE_CCTX.invokeExact();
Expand Down
15 changes: 15 additions & 0 deletions zstd/src/test/java/io/github/dfa1/zstd/ZstdTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,21 @@ void rejectsOversizedFrameForBuffer() {
// Then it fails
assertThatThrownBy(result).isInstanceOf(ZstdException.class);
}

@Test
void rejectsNullInputWithANamedMessage() {
// When null is passed where bytes are required
ThrowingCallable compressNull = () -> Zstd.compress(null);
ThrowingCallable decompressNull = () -> Zstd.decompress(null);

// Then it fails fast with a NullPointerException naming the parameter
assertThatThrownBy(compressNull)
.isInstanceOf(NullPointerException.class)
.hasMessageContaining("src");
assertThatThrownBy(decompressNull)
.isInstanceOf(NullPointerException.class)
.hasMessageContaining("compressed");
}
}

@Nested
Expand Down
Loading