Skip to content
Open
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
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ AsyncHttpClient client = asyncHttpClient(config().setUseNativeTransport(true));
<dependency>
<groupId>com.github.luben</groupId>
<artifactId>zstd-jni</artifactId>
<version>1.5.7-7</version>
<version>1.5.7-26</version>
</dependency>
```

Expand All @@ -118,10 +118,11 @@ deflate are not affected.
Over HTTP/1.1, a compressed response body that ends in the middle of its compressed stream, for example because a
body delimited by the connection was cut off, fails the request with a `DecompressionException`; a body without any
bytes completes. Not every such end is noticed: Snappy has no end marker, so a body cut between two of its chunks looks
complete, and a Zstd body of several frames completes when it is cut inside a frame that started in the same chunk as
the end of the previous one. HTTP/2 response bodies are not checked, and neither are transfer codings such as
`Transfer-Encoding: gzip`, nor Brotli, Zstd and Snappy bodies decompressed as soon as they are read, as described
above.
complete. With zstd-jni older than 1.5.7-26, a Zstd body of several frames also completes when it is cut inside a frame
that started in the same chunk as the end of the previous one. zstd-jni is an optional dependency, so the version your
application declares is the one used: use 1.5.7-26 or later. HTTP/2 response bodies are not checked, and neither are
transfer codings such as `Transfer-Encoding: gzip`, nor Brotli, Zstd and Snappy bodies decompressed as soon as they are
read, as described above.

</details>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,9 @@
* {@code zstd} also goes there while {@code io.netty.compression.defaultMaxForwardBytes} is not positive.
* {@code gzip} and {@code deflate} never use the API. A body that ends inside its compressed stream fails on the
* pull path too, and still ends quietly on the paths through {@link HttpContentDecompressor}. Not every such end is
* noticed: {@code snappy} has no end marker, so a body cut between two of its chunks looks complete, and a
* {@code zstd} body of several frames completes when it is cut inside a frame that started in the same chunk as the
* end of the previous one.
* noticed: {@code snappy} has no end marker, so a body cut between two of its chunks looks complete, and with
* zstd-jni before 1.5.7-26, a {@code zstd} body of several frames completes when it is cut inside a frame that started
* in the same chunk as the end of the previous one.
* <p>
* Every other content coding, and a transfer coding such as {@code Transfer-Encoding: gzip}, still goes through
* {@link HttpContentDecompressor}, which decompresses it as soon as it is read. There the counting is done
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1279,6 +1279,52 @@ void truncatedBrZstdAndSnappyBodiesFailAndBodiesWithoutBytesEnd() {
assertTrue(checked > 30, "only " + checked + " scenarios ran");
}

private static final byte[] FIRST_ZSTD_FRAME = Zstd.compress(Arrays.copyOf(RANDOM, 20_000));
private static final byte[] SECOND_ZSTD_FRAME = Zstd.compress(Arrays.copyOfRange(RANDOM, 20_000, 40_000));

// Each ends the first frame and starts the second in the same chunk.
private static List<int[]> splitsAcrossTheFirstZstdFrameEnd(int length) {
int half = FIRST_ZSTD_FRAME.length / 2;
return List.of(new int[]{length}, new int[]{length, 0}, new int[]{half, length - half});
}

@Test
void zstdBodyCutInAFrameThatStartedInTheChunkThatEndedThePreviousOneFails() {
byte[] cut = concat(FIRST_ZSTD_FRAME, Arrays.copyOf(SECOND_ZSTD_FRAME, SECOND_ZSTD_FRAME.length / 2));
for (int[] sizes : splitsAcrossTheFirstZstdFrameEnd(cut.length)) {
for (Kind kind : List.of(Kind.HEAP, Kind.DIRECT)) {
String scenario = Arrays.toString(sizes) + ", " + kind;
UnpooledByteBufAllocator allocator = allocator();
EmbeddedChannel channel = channel(new Http1ContentDecompressor(false, 0), allocator);
Outcome outcome = exchange(channel, "zstd", cut, sizes, kind);
assertInstanceOf(DecompressionException.class, outcome.failure, scenario);
assertFalse(outcome.ended, scenario + ": a truncated body must not end normally");
channel.finishAndReleaseAll();
assertEquals(0, allocator.metric().usedHeapMemory(), scenario + ": heap left allocated");
assertInputsReleased(outcome, scenario);
}
}
}

@Test
void zstdFramesThatShareAChunkDecodeInFull() {
byte[] body = concat(FIRST_ZSTD_FRAME, SECOND_ZSTD_FRAME);
for (int[] sizes : splitsAcrossTheFirstZstdFrameEnd(body.length)) {
for (Kind kind : List.of(Kind.HEAP, Kind.DIRECT)) {
String scenario = Arrays.toString(sizes) + ", " + kind;
UnpooledByteBufAllocator allocator = allocator();
EmbeddedChannel channel = channel(new Http1ContentDecompressor(false, 0), allocator);
Outcome outcome = exchange(channel, "zstd", body, sizes, kind);
assertNull(outcome.failure, scenario);
assertTrue(outcome.ended, scenario);
assertArrayEquals(Arrays.copyOf(RANDOM, 40_000), outcome.body.toByteArray(), scenario);
assertFalse(channel.finishAndReleaseAll(), scenario);
assertEquals(0, allocator.metric().usedHeapMemory(), scenario + ": heap left allocated");
assertInputsReleased(outcome, scenario);
}
}
}

@Test
void outputThatOnlyFollowsTheEndOfInputIsHandedOn() {
UnpooledByteBufAllocator allocator = allocator();
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
<netty.version>4.2.20.Final</netty.version>
<brotli4j.version>1.24.0</brotli4j.version>
<slf4j.version>2.0.20</slf4j.version>
<zstd-jni.version>1.5.7-25</zstd-jni.version>
<zstd-jni.version>1.5.7-26</zstd-jni.version>
<logback.version>1.6.6</logback.version>
<jetbrains-annotations.version>26.1.0</jetbrains-annotations.version>
<testcontainers.version>2.0.5</testcontainers.version>
Expand Down
Loading