|
6 | 6 |
|
7 | 7 | import com.southernstorm.noise.protocol.CipherState;
|
8 | 8 | import com.southernstorm.noise.protocol.CipherStatePair;
|
| 9 | +import com.southernstorm.noise.protocol.Noise; |
9 | 10 | import io.netty.buffer.ByteBuf;
|
10 | 11 | import io.netty.buffer.ByteBufUtil;
|
11 | 12 | import io.netty.buffer.Unpooled;
|
|
16 | 17 | import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
|
17 | 18 | import io.netty.handler.codec.http.websocketx.WebSocketFrame;
|
18 | 19 | import io.netty.util.ReferenceCountUtil;
|
| 20 | +import io.netty.util.concurrent.PromiseCombiner; |
19 | 21 | import io.netty.util.internal.EmptyArrays;
|
20 | 22 | import java.util.Optional;
|
21 | 23 | import java.util.concurrent.CompletableFuture;
|
@@ -163,25 +165,35 @@ private void fail(final ChannelHandlerContext context, final Throwable cause) {
|
163 | 165 | @Override
|
164 | 166 | public void write(final ChannelHandlerContext context, final Object message, final ChannelPromise promise)
|
165 | 167 | throws Exception {
|
166 |
| - if (message instanceof ByteBuf plaintext) { |
| 168 | + if (message instanceof ByteBuf byteBuf) { |
167 | 169 | try {
|
168 | 170 | // TODO Buffer/consolidate Noise writes to avoid sending a bazillion tiny (or empty) frames
|
169 | 171 | final CipherState cipherState = cipherStatePair.getSender();
|
170 |
| - final int plaintextLength = plaintext.readableBytes(); |
171 | 172 |
|
172 |
| - // We've read these bytes from a local connection; although that likely means they're backed by a heap array, the |
173 |
| - // buffer is read-only and won't grant us access to the underlying array. Instead, we need to copy the bytes to a |
174 |
| - // mutable array. We also want to encrypt in place, so we allocate enough extra space for the trailing MAC. |
175 |
| - final byte[] noiseBuffer = new byte[plaintext.readableBytes() + cipherState.getMACLength()]; |
176 |
| - plaintext.readBytes(noiseBuffer, 0, plaintext.readableBytes()); |
| 173 | + // Server message might not fit in a single noise packet, break it up into as many chunks as we need |
| 174 | + final PromiseCombiner pc = new PromiseCombiner(context.executor()); |
| 175 | + while (byteBuf.isReadable()) { |
| 176 | + final ByteBuf plaintext = byteBuf.readSlice(Math.min( |
| 177 | + // need room for a 16-byte AEAD tag |
| 178 | + Noise.MAX_PACKET_LEN - 16, |
| 179 | + byteBuf.readableBytes())); |
177 | 180 |
|
178 |
| - // Overwrite the plaintext with the ciphertext to avoid an extra allocation for a dedicated ciphertext buffer |
179 |
| - cipherState.encryptWithAd(null, noiseBuffer, 0, noiseBuffer, 0, plaintextLength); |
| 181 | + final int plaintextLength = plaintext.readableBytes(); |
180 | 182 |
|
181 |
| - context.write(new BinaryWebSocketFrame(Unpooled.wrappedBuffer(noiseBuffer)), promise); |
| 183 | + // We've read these bytes from a local connection; although that likely means they're backed by a heap array, the |
| 184 | + // buffer is read-only and won't grant us access to the underlying array. Instead, we need to copy the bytes to a |
| 185 | + // mutable array. We also want to encrypt in place, so we allocate enough extra space for the trailing MAC. |
| 186 | + final byte[] noiseBuffer = new byte[plaintext.readableBytes() + cipherState.getMACLength()]; |
| 187 | + plaintext.readBytes(noiseBuffer, 0, plaintext.readableBytes()); |
182 | 188 |
|
| 189 | + // Overwrite the plaintext with the ciphertext to avoid an extra allocation for a dedicated ciphertext buffer |
| 190 | + cipherState.encryptWithAd(null, noiseBuffer, 0, noiseBuffer, 0, plaintextLength); |
| 191 | + |
| 192 | + pc.add(context.write(new BinaryWebSocketFrame(Unpooled.wrappedBuffer(noiseBuffer)))); |
| 193 | + } |
| 194 | + pc.finish(promise); |
183 | 195 | } finally {
|
184 |
| - ReferenceCountUtil.release(plaintext); |
| 196 | + ReferenceCountUtil.release(byteBuf); |
185 | 197 | }
|
186 | 198 | } else {
|
187 | 199 | if (!(message instanceof WebSocketFrame)) {
|
|
0 commit comments