Skip to content

Commit 61980ee

Browse files
mtopolnikclaude
andcommitted
Fix WebSocket mask key byte order
WebSocketFrameWriter.writeHeader() wrote the mask key via Unsafe.putInt() in native byte order. maskPayload() also extracted mask bytes in native order. RFC 6455 specifies network (big-endian) byte order for the mask key on the wire. writeHeader() now writes the 4 mask key bytes individually in big-endian order. maskPayload() converts to native order for bulk XOR and extracts per-byte mask in big-endian order. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent d76aa02 commit 61980ee

1 file changed

Lines changed: 17 additions & 6 deletions

File tree

core/src/main/java/io/questdb/client/cutlass/qwp/websocket/WebSocketFrameWriter.java

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626

2727
import io.questdb.client.std.Unsafe;
2828

29+
import java.nio.ByteOrder;
30+
2931
/**
3032
* Zero-allocation WebSocket frame writer.
3133
* Writes WebSocket frames according to RFC 6455.
@@ -37,6 +39,7 @@
3739
public final class WebSocketFrameWriter {
3840
// Frame header bits
3941
private static final int FIN_BIT = 0x80;
42+
private static final boolean IS_BIG_ENDIAN = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN;
4043
private static final int MASK_BIT = 0x80;
4144

4245
private WebSocketFrameWriter() {
@@ -70,9 +73,13 @@ public static int headerSize(long payloadLength, boolean masked) {
7073
* @param maskKey the 4-byte mask key
7174
*/
7275
public static void maskPayload(long buf, long len, int maskKey) {
73-
// Process 8 bytes at a time when possible
76+
// maskKey is in big-endian convention: MSB = wire byte 0 = mask byte for position 0.
77+
// For bulk XOR via getInt/getLong (native byte order), convert to native order
78+
// so that memory position 0 XORs with mask byte 0, position 1 with mask byte 1, etc.
79+
int nativeMask = IS_BIG_ENDIAN ? maskKey : Integer.reverseBytes(maskKey);
80+
long longMask = ((long) nativeMask << 32) | (nativeMask & 0xFFFFFFFFL);
81+
7482
long i = 0;
75-
long longMask = ((long) maskKey << 32) | (maskKey & 0xFFFFFFFFL);
7683

7784
// Process 8-byte chunks
7885
while (i + 8 <= len) {
@@ -84,14 +91,14 @@ public static void maskPayload(long buf, long len, int maskKey) {
8491
// Process 4-byte chunk if remaining
8592
if (i + 4 <= len) {
8693
int value = Unsafe.getUnsafe().getInt(buf + i);
87-
Unsafe.getUnsafe().putInt(buf + i, value ^ maskKey);
94+
Unsafe.getUnsafe().putInt(buf + i, value ^ nativeMask);
8895
i += 4;
8996
}
9097

91-
// Process remaining bytes (0-3 bytes) - extract mask byte inline to avoid allocation
98+
// Process remaining bytes - extract mask byte in big-endian order
9299
while (i < len) {
93100
byte b = Unsafe.getUnsafe().getByte(buf + i);
94-
int maskByte = (maskKey >> (((int) i & 3) << 3)) & 0xFF;
101+
int maskByte = (maskKey >>> ((3 - ((int) i & 3)) << 3)) & 0xFF;
95102
Unsafe.getUnsafe().putByte(buf + i, (byte) (b ^ maskByte));
96103
i++;
97104
}
@@ -144,7 +151,11 @@ public static int writeHeader(long buf, boolean fin, int opcode, long payloadLen
144151
*/
145152
public static int writeHeader(long buf, boolean fin, int opcode, long payloadLength, int maskKey) {
146153
int offset = writeHeader(buf, fin, opcode, payloadLength, true);
147-
Unsafe.getUnsafe().putInt(buf + offset, maskKey);
154+
// Write mask key in network byte order (big-endian) per RFC 6455
155+
Unsafe.getUnsafe().putByte(buf + offset, (byte) (maskKey >>> 24));
156+
Unsafe.getUnsafe().putByte(buf + offset + 1, (byte) (maskKey >>> 16));
157+
Unsafe.getUnsafe().putByte(buf + offset + 2, (byte) (maskKey >>> 8));
158+
Unsafe.getUnsafe().putByte(buf + offset + 3, (byte) maskKey);
148159
return offset + 4;
149160
}
150161
}

0 commit comments

Comments
 (0)