Skip to content

Commit 59a8bd0

Browse files
authored
Snappy: Use unsigned short to handle 2 ^ 16 input size instead of 2 ^ 15 (#13828)
Motivation: Snappy compressor to be able to compress a much larger amount than 32767 bytes. Modifications: - Avoid iterating to create correct size hash table (limited to MAX_HT_SIZE) - Configurable snappy frame size for the encoder - Added static factory method for SnappyEncoder supporting 2 ^ 16 size frame slices - Added integration test Result: Fixes #13226
1 parent 14e00f5 commit 59a8bd0

3 files changed

Lines changed: 71 additions & 10 deletions

File tree

codec/src/main/java/io/netty/handler/codec/compression/Snappy.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package io.netty.handler.codec.compression;
1717

1818
import io.netty.buffer.ByteBuf;
19+
import io.netty.util.internal.MathUtil;
1920

2021
/**
2122
* Uncompresses an input {@link ByteBuf} encoded with Snappy compression into an
@@ -95,7 +96,9 @@ public void encode(final ByteBuf in, final ByteBuf out, final int length) {
9596

9697
nextHash = hash(in, nextIndex, shift);
9798

98-
candidate = baseIndex + table[hash];
99+
// equivalent to Short.toUnsignedInt
100+
// use unsigned short cast to avoid loss precision when 32767 <= length <= 65355
101+
candidate = baseIndex + ((int) table[hash]) & 0xffff;
99102

100103
table[hash] = (short) (inIndex - baseIndex);
101104
}
@@ -157,11 +160,8 @@ private static int hash(ByteBuf in, int index, int shift) {
157160
* @return An appropriately sized empty hashtable
158161
*/
159162
private static short[] getHashTable(int inputSize) {
160-
int htSize = 256;
161-
while (htSize < MAX_HT_SIZE && htSize < inputSize) {
162-
htSize <<= 1;
163-
}
164-
return new short[htSize];
163+
int hashTableSize = MathUtil.findNextPositivePowerOfTwo(inputSize);
164+
return new short[Math.min(hashTableSize, MAX_HT_SIZE)];
165165
}
166166

167167
/**

codec/src/main/java/io/netty/handler/codec/compression/SnappyFrameEncoder.java

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,17 @@
2727
* See <a href="https://github.com/google/snappy/blob/master/framing_format.txt">Snappy framing format</a>.
2828
*/
2929
public class SnappyFrameEncoder extends MessageToByteEncoder<ByteBuf> {
30+
31+
private static final short SNAPPY_SLICE_SIZE = Short.MAX_VALUE;
32+
33+
/**
34+
* Both
35+
* {@value io.netty.handler.codec.compression.SnappyFrameEncoder#SNAPPY_SLICE_SIZE}
36+
* and {@value io.netty.handler.codec.compression.SnappyFrameEncoder#SNAPPY_SLICE_JUMBO_SIZE}
37+
* are valid lengths for the Snappy framing format
38+
*/
39+
private static final int SNAPPY_SLICE_JUMBO_SIZE = 65535;
40+
3041
/**
3142
* The minimum amount that we'll consider actually attempting to compress.
3243
* This value is preamble + the minimum length our Snappy service will
@@ -42,8 +53,26 @@ public class SnappyFrameEncoder extends MessageToByteEncoder<ByteBuf> {
4253
(byte) 0xff, 0x06, 0x00, 0x00, 0x73, 0x4e, 0x61, 0x50, 0x70, 0x59
4354
};
4455

56+
public SnappyFrameEncoder() {
57+
this(SNAPPY_SLICE_SIZE);
58+
}
59+
60+
/**
61+
* Create a new instance with a
62+
* {@value io.netty.handler.codec.compression.SnappyFrameEncoder#SNAPPY_SLICE_JUMBO_SIZE}
63+
* chunk size.
64+
*/
65+
public static SnappyFrameEncoder snappyEncoderWithJumboFrames() {
66+
return new SnappyFrameEncoder(SNAPPY_SLICE_JUMBO_SIZE);
67+
}
68+
69+
private SnappyFrameEncoder(int sliceSize) {
70+
this.sliceSize = sliceSize;
71+
}
72+
4573
private final Snappy snappy = new Snappy();
4674
private boolean started;
75+
private final int sliceSize;
4776

4877
@Override
4978
protected void encode(ChannelHandlerContext ctx, ByteBuf in, ByteBuf out) throws Exception {
@@ -67,12 +96,12 @@ protected void encode(ChannelHandlerContext ctx, ByteBuf in, ByteBuf out) throws
6796
}
6897

6998
out.writeInt(0);
70-
if (dataLength > Short.MAX_VALUE) {
71-
ByteBuf slice = in.readSlice(Short.MAX_VALUE);
99+
if (dataLength > sliceSize) {
100+
ByteBuf slice = in.readSlice(sliceSize);
72101
calculateAndWriteChecksum(slice, out);
73-
snappy.encode(slice, out, Short.MAX_VALUE);
102+
snappy.encode(slice, out, sliceSize);
74103
setChunkLength(out, lengthIdx);
75-
dataLength -= Short.MAX_VALUE;
104+
dataLength -= sliceSize;
76105
} else {
77106
ByteBuf slice = in.readSlice(dataLength);
78107
calculateAndWriteChecksum(slice, out);
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright 2024 The Netty Project
3+
*
4+
* The Netty Project licenses this file to you under the Apache License,
5+
* version 2.0 (the "License"); you may not use this file except in compliance
6+
* with the License. You may obtain a copy of the License at:
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations
14+
* under the License.
15+
*/
16+
package io.netty.handler.codec.compression;
17+
18+
import io.netty.channel.embedded.EmbeddedChannel;
19+
20+
public class SnappyJumboSizeIntegrationTest extends AbstractIntegrationTest {
21+
22+
@Override
23+
protected EmbeddedChannel createEncoder() {
24+
return new EmbeddedChannel(SnappyFrameEncoder.snappyEncoderWithJumboFrames());
25+
}
26+
27+
@Override
28+
protected EmbeddedChannel createDecoder() {
29+
return new EmbeddedChannel(new SnappyFrameDecoder());
30+
}
31+
32+
}

0 commit comments

Comments
 (0)