From 0aed6ecf72de56773b41335f65f6b65bb84e45b3 Mon Sep 17 00:00:00 2001 From: Geod24 Date: Tue, 19 Jul 2016 02:24:29 +0200 Subject: [PATCH 01/10] Only run travis CI when there is a dub.{json,sdl} --- travis-ci.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/travis-ci.sh b/travis-ci.sh index ef6cea61ee..b9669cc98e 100755 --- a/travis-ci.sh +++ b/travis-ci.sh @@ -20,7 +20,9 @@ if [ ${BUILD_EXAMPLE=1} -eq 1 ]; then fi if [ ${RUN_TEST=1} -eq 1 ]; then for ex in `\ls -1 tests/`; do - echo "[INFO] Running test $ex" - (cd tests/$ex && dub --compiler=$DC && dub clean) + if [ -r test/$ex/dub.json ] || [ -r test/$ex/dub.sdl ]; then + echo "[INFO] Running test $ex" + (cd tests/$ex && dub --compiler=$DC && dub clean) + fi done fi From 56185683a730181f1d99fcefbcd720412b0cea22 Mon Sep 17 00:00:00 2001 From: Geod24 Date: Mon, 6 Jun 2016 03:29:03 +0200 Subject: [PATCH 02/10] Add an application to test the client websockets implementation against autobahn Autobahn (http://autobahn.ws/testsuite) is a comprehensive test suite of the Websocket specifications. This test won't be run by default as it requires way too much ressources to put it into the CI, but can trivially be run manually by anyone wanting to test the websockets implementation for conformity. --- .../.gitignore | 6 +++ .../dub.json | 9 ++++ .../source/app.d | 41 +++++++++++++++++++ 3 files changed, 56 insertions(+) create mode 100644 tests/not-runnable/vibe.http.websocket-autobahn-client/.gitignore create mode 100644 tests/not-runnable/vibe.http.websocket-autobahn-client/dub.json create mode 100644 tests/not-runnable/vibe.http.websocket-autobahn-client/source/app.d diff --git a/tests/not-runnable/vibe.http.websocket-autobahn-client/.gitignore b/tests/not-runnable/vibe.http.websocket-autobahn-client/.gitignore new file mode 100644 index 0000000000..103a905478 --- /dev/null +++ b/tests/not-runnable/vibe.http.websocket-autobahn-client/.gitignore @@ -0,0 +1,6 @@ +websockets-autobahn-client +.dub +docs.json +__dummy.html +*.o +*.obj diff --git a/tests/not-runnable/vibe.http.websocket-autobahn-client/dub.json b/tests/not-runnable/vibe.http.websocket-autobahn-client/dub.json new file mode 100644 index 0000000000..b383a157b7 --- /dev/null +++ b/tests/not-runnable/vibe.http.websocket-autobahn-client/dub.json @@ -0,0 +1,9 @@ +{ + "name": "websockets-autobahn-client", + "dependencies": { + "vibe-d": { "path": "../../" } + }, + "versions": [ + "VibeDefaultMain" + ] +} diff --git a/tests/not-runnable/vibe.http.websocket-autobahn-client/source/app.d b/tests/not-runnable/vibe.http.websocket-autobahn-client/source/app.d new file mode 100644 index 0000000000..5966167fc1 --- /dev/null +++ b/tests/not-runnable/vibe.http.websocket-autobahn-client/source/app.d @@ -0,0 +1,41 @@ +import vibe.d; + +shared static this () +{ + runTask(() => runTestSuite()); +} + +void runTestSuite () +{ + auto count = getCaseCount(); + logInfo("We're going to run %d test cases...", count); + + foreach (currCase; 1 .. count) + { + auto url = URL("ws://127.0.0.1:9001/runCase?agent=vibe.d&case=" + ~ to!string(currCase)); + logInfo("Running test case %d/%d", currCase, count); + connectWebSocket( + url, (scope ws) { + while (ws.waitForData) { + ws.receive((scope message) { + ws.send(message.readAll); + }); + } + }); + } +} + + +size_t getCaseCount (string base_addr = "ws://127.0.0.1:9001") +{ + size_t ret; + auto url = URL(base_addr ~ "/getCaseCount"); + connectWebSocket( + url, (scope ws) { + while (ws.waitForData) { + ret = ws.receiveText.to!size_t; + } + }); + return ret; +} From ed4370c0111ba00b3fc6e1874a8c2f0e60c3e083 Mon Sep 17 00:00:00 2001 From: Geod24 Date: Fri, 10 Jun 2016 00:20:02 +0200 Subject: [PATCH 03/10] websocket: Allow const `ubyte[]` to be sent --- http/vibe/http/websockets.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/http/vibe/http/websockets.d b/http/vibe/http/websockets.d index 48cb370c8f..41046e23b0 100644 --- a/http/vibe/http/websockets.d +++ b/http/vibe/http/websockets.d @@ -409,7 +409,7 @@ final class WebSocket { On the JavaScript side, the text will be available as message.data (type Blob). Throws: WebSocketException if the connection is closed. */ - void send(ubyte[] data) + void send(in ubyte[] data) { send((scope message){ message.write(data); }, FrameOpcode.binary); } From ac2ebba08bb39439e286e8ae8225f8c080ebbc36 Mon Sep 17 00:00:00 2001 From: Geod24 Date: Sun, 17 Jul 2016 17:47:50 +0200 Subject: [PATCH 04/10] WebSocket: Add annotation to WebSocketException ctor --- http/vibe/http/websockets.d | 3 +++ 1 file changed, 3 insertions(+) diff --git a/http/vibe/http/websockets.d b/http/vibe/http/websockets.d index 41046e23b0..c92db5c46b 100644 --- a/http/vibe/http/websockets.d +++ b/http/vibe/http/websockets.d @@ -57,6 +57,9 @@ import vibe.crypto.cryptorand; /// Exception thrown by $(D vibe.http.websockets). class WebSocketException: Exception { + // @nogc cannot be applied to 2.067 because Exception.ctor is not @nogc + @safe pure nothrow: + /// this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { From 62ce9b127063d5527908edd10174617c95f925ac Mon Sep 17 00:00:00 2001 From: Geod24 Date: Sun, 17 Jul 2016 19:28:51 +0200 Subject: [PATCH 05/10] Websockets: Make debug message a bit more informative --- http/vibe/http/websockets.d | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/http/vibe/http/websockets.d b/http/vibe/http/websockets.d index c92db5c46b..f8200a0aff 100644 --- a/http/vibe/http/websockets.d +++ b/http/vibe/http/websockets.d @@ -778,7 +778,6 @@ struct Frame { bool masked = (data2[1] & 0x80) == 0x80; frame.opcode = cast(FrameOpcode)(data2[0] & 0xf); - logDebug("Read frame: %s %s", frame.opcode, frame.fin); //parsing length ulong length = data2[1] & 0x7f; if( length == 126 ) { @@ -788,6 +787,11 @@ struct Frame { stream.read(data8); length = bigEndianToNative!ulong(data8); } + logDebug("Read frame: %s %s %s length=%d", + frame.opcode, + frame.fin ? "final frame" : "continuation", + masked ? "masked" : "not masked", + length); //masking key ubyte[4] maskingKey; From af43c4afe57c8ada842208566a4f1f1f5f263099 Mon Sep 17 00:00:00 2001 From: Geod24 Date: Sun, 17 Jul 2016 18:47:48 +0200 Subject: [PATCH 06/10] WebSocket: Add IncomingWebSocketMessage.nextFrame --- http/vibe/http/websockets.d | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/http/vibe/http/websockets.d b/http/vibe/http/websockets.d index f8200a0aff..b6eff9568f 100644 --- a/http/vibe/http/websockets.d +++ b/http/vibe/http/websockets.d @@ -657,6 +657,27 @@ final class IncomingWebSocketMessage : InputStream { const(ubyte)[] peek() { return m_currentFrame.payload; } + /** + * Retrieve the next websocket frame of the stream and discard the current + * one + * + * This function is helpful if one wish to process frames by frames, + * or minimize memory allocation, as `peek` will only return the current + * frame data, and read requires a pre-allocated buffer. + * + * Returns: + * `false` if the current frame is the final one, `true` if a new frame + * was read. + */ + bool skipFrame() + { + if (m_currentFrame.fin) + return false; + + m_currentFrame = Frame.readFrame(m_conn); + return true; + } + void read(ubyte[] dst) { while( dst.length > 0 ) { @@ -669,7 +690,8 @@ final class IncomingWebSocketMessage : InputStream { dst = dst[sz .. $]; m_currentFrame.payload = m_currentFrame.payload[sz .. $]; - if( leastSize == 0 && !m_currentFrame.fin ) m_currentFrame = Frame.readFrame(m_conn); + if (leastSize == 0) + this.skipFrame(); } } From 13eb41383634878f3401585c0fd2fd5795ff561d Mon Sep 17 00:00:00 2001 From: Geod24 Date: Sun, 17 Jul 2016 19:43:16 +0200 Subject: [PATCH 07/10] Websocket: Minor style fixes (tabify) --- http/vibe/http/websockets.d | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/http/vibe/http/websockets.d b/http/vibe/http/websockets.d index b6eff9568f..f461e652c3 100644 --- a/http/vibe/http/websockets.d +++ b/http/vibe/http/websockets.d @@ -1,7 +1,7 @@ /** Implements WebSocket support and fallbacks for older browsers. - Standards: $(LINK2 https://tools.ietf.org/html/rfc6455, RFC6455) + Standards: $(LINK2 https://tools.ietf.org/html/rfc6455, RFC6455) Copyright: © 2012-2014 RejectedSoftware e.K. License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. Authors: Jan Krüger @@ -311,7 +311,9 @@ final class WebSocket { uint m_lastPingIndex; bool m_pongReceived; bool m_pongSkipped; - SystemRNG m_rng; + /// The entropy generator to use + /// If not null, it means this is a server socket. + SystemRNG m_rng; } /** @@ -574,7 +576,7 @@ final class WebSocket { */ final class OutgoingWebSocketMessage : OutputStream { private { - SystemRNG m_rng; + SystemRNG m_rng; Stream m_conn; FrameOpcode m_frameOpcode; Appender!(ubyte[]) m_buffer; @@ -586,7 +588,7 @@ final class OutgoingWebSocketMessage : OutputStream { assert(conn !is null); m_conn = conn; m_frameOpcode = frameOpcode; - m_rng = rng; + m_rng = rng; } void write(in ubyte[] bytes) @@ -633,7 +635,7 @@ final class OutgoingWebSocketMessage : OutputStream { */ final class IncomingWebSocketMessage : InputStream { private { - SystemRNG m_rng; + SystemRNG m_rng; Stream m_conn; Frame m_currentFrame; } @@ -642,8 +644,8 @@ final class IncomingWebSocketMessage : InputStream { { assert(conn !is null); m_conn = conn; - m_rng = rng; - readFrame(); + m_rng = rng; + readFrame(); } @property bool empty() const { return m_currentFrame.payload.length == 0; } @@ -722,8 +724,9 @@ final class IncomingWebSocketMessage : InputStream { } } +/// Magic string defined by the RFC for challenging the server during upgrade +private static immutable s_webSocketGuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; -private immutable s_webSocketGuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; /** * The Opcode is 4 bytes, as defined in Section 5.2 @@ -753,7 +756,7 @@ struct Frame { auto rng = StreamOutputRange(stream); - ubyte[4] buff; + ubyte[4] buff; ubyte firstByte = cast(ubyte)opcode; if (fin) firstByte |= 0x80; rng.put(firstByte); @@ -766,17 +769,17 @@ struct Frame { if( payload.length < 126 ) { rng.put(std.bitmanip.nativeToBigEndian(cast(ubyte)(b1 | payload.length))); } else if( payload.length <= 65536 ) { - buff[0] = cast(ubyte) (b1 | 126); + buff[0] = cast(ubyte) (b1 | 126); rng.put(buff[0 .. 1]); rng.put(std.bitmanip.nativeToBigEndian(cast(ushort)payload.length)); } else { - buff[0] = cast(ubyte) (b1 | 127); + buff[0] = cast(ubyte) (b1 | 127); rng.put(buff[0 .. 1]); rng.put(std.bitmanip.nativeToBigEndian(payload.length)); } if (sys_rng) { - sys_rng.read(buff); + sys_rng.read(buff); rng.put(buff); for (size_t i = 0; i < payload.length; i++) { payload[i] ^= buff[i % 4]; From f36231d7ddb8da92d81b473720af0dad10346889 Mon Sep 17 00:00:00 2001 From: Geod24 Date: Sun, 17 Jul 2016 19:47:43 +0200 Subject: [PATCH 08/10] WebSocket: Deprecate default FrameOpcode parameter on send We should not assume what the user is sending, as it varies from one application to another. The default might as well be binary and would seem more sensible to some. --- http/vibe/http/websockets.d | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/http/vibe/http/websockets.d b/http/vibe/http/websockets.d index f461e652c3..68627f71a9 100644 --- a/http/vibe/http/websockets.d +++ b/http/vibe/http/websockets.d @@ -405,7 +405,9 @@ final class WebSocket { */ void send(scope const(char)[] data) { - send((scope message){ message.write(cast(const ubyte[])data); }); + send( + (scope message) { message.write(cast(const ubyte[])data); }, + FrameOpcode.text); } /** @@ -423,7 +425,8 @@ final class WebSocket { Sends a message using an output stream. Throws: WebSocketException if the connection is closed. */ - void send(scope void delegate(scope OutgoingWebSocketMessage) sender, FrameOpcode frameOpcode = FrameOpcode.text) + void send(scope void delegate(scope OutgoingWebSocketMessage) sender, + FrameOpcode frameOpcode) { m_writeMutex.performLocked!({ enforceEx!WebSocketException(!m_sentCloseFrame, "WebSocket connection already actively closed."); @@ -433,6 +436,13 @@ final class WebSocket { }); } + /// Compatibility overload - will be removed soon. + deprecated("Call the overload which requires an explicit FrameOpcode.") + void send(scope void delegate(scope OutgoingWebSocketMessage) sender) + { + send(sender, FrameOpcode.text); + } + /** Actively closes the connection. From 4afc22b714e4fa8f0df98346be924fb84cce3407 Mon Sep 17 00:00:00 2001 From: Geod24 Date: Sun, 17 Jul 2016 20:18:24 +0200 Subject: [PATCH 09/10] Websocket: Improve Frame.writeFrame code Reduce the need for additional buffer, opening the way for more memory improvements, and document what it is doing. Also corrects a bug with length == 65536, as it would be cast to `ushort` which maximum size is 65535. Finally, fixup documentation and type of FrameOpcode. --- http/vibe/http/websockets.d | 159 ++++++++++++++++++++++++++++++------ 1 file changed, 136 insertions(+), 23 deletions(-) diff --git a/http/vibe/http/websockets.d b/http/vibe/http/websockets.d index 68627f71a9..195e68bfb2 100644 --- a/http/vibe/http/websockets.d +++ b/http/vibe/http/websockets.d @@ -739,13 +739,13 @@ private static immutable s_webSocketGuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11 /** - * The Opcode is 4 bytes, as defined in Section 5.2 + * The Opcode is 4 bits, as defined in Section 5.2 * * Values are defined in section 11.8 * Currently only 6 values are defined, however the opcode is defined as - * taking 4 bytes. + * taking 4 bits. */ -enum FrameOpcode : uint { +enum FrameOpcode : ubyte { continuation = 0x0, text = 0x1, binary = 0x2, @@ -753,51 +753,164 @@ enum FrameOpcode : uint { ping = 0x9, pong = 0xA } +static assert(FrameOpcode.max < 0b1111, "FrameOpcode is only 4 bits"); struct Frame { - bool fin; - FrameOpcode opcode; + /** + Contains the first byte of the frame: + - The FIN bit + - The 3 reserved bits RSV1, RSV2, RSV3 + - The 4-bits opcode + */ + ubyte fin_rsv_opcode; ubyte[] payload; - void writeFrame(OutputStream stream, SystemRNG sys_rng) + /** + * Get or set the 'final' bit of this frame, + * which is the first bit of the frame + */ + @property bool fin (bool n) pure nothrow @safe @nogc + { + if (n) + this.fin_rsv_opcode |= 0b1000_0000; + else + this.fin_rsv_opcode &= 0b0111_1111; + return n; + } + + /// Ditto + @property bool fin () const pure nothrow @safe @nogc + { + return !!(this.fin_rsv_opcode & 0b1000_0000); + } + + + /** + * Get or set the opcode of this frame, + * which is the lower 4 bits of the first byte + */ + @property FrameOpcode opcode (FrameOpcode n) pure nothrow @safe @nogc + { + this.fin_rsv_opcode = (this.fin_rsv_opcode & 0b1111_0000) | n; + return n; + } + + /// Ditto + @property FrameOpcode opcode () const pure nothrow @safe @nogc + { + return cast(FrameOpcode)(this.fin_rsv_opcode & 0b0000_1111); + } + + /** + * Return the payload length encoded with the expected amount of bits + * + * The WebSocket RFC define a variable-length payload length. + * In short, it means that: + * - If the length is <= 125, it is stored as the 7 least significant + * bits of the second header byte. The first bit is reserved for MASK. + * - If the length is <= 65_536 (so it fits in 2 bytes), a magic value of + * 126 is stored in the aforementioned 7 bits, and the actual length + * is stored in the next two bytes, resulting in a 4 bytes header + * ( + masking key, if any). + * - If the length is > 65_536, a magic value of 127 will be used for + * the 7-bit field, and the next 8 bytes are expected to be the length, + * resulting in a 10 bytes header ( + masking key, if any). + * + * Those functions encapsulate all this logic and allow to just get the + * length with the desired size. + * + * Return: + * - For `ubyte`, the value to store in the 7 bits field, either the + * length or a magic value (126 or 127). + * - For `ushort`, a value in the range [126; 65_536]. + * If payload.length is not in this bound, an assertion will be triggered. + * - For `ulong`, a value in the range [65_537; size_t.max]. + * If payload.length is not in this bound, an assertion will be triggered. + */ + private ubyte smallPayloadLength(bool masked) const pure nothrow @safe @nogc + { + // Note: If length == 126, we need to use the 2 bytes anyway + const ubyte m = masked ? 0b1000_0000 : 0; + if (this.payload.length > ushort.max) + return 127 | m; + else if (this.payload.length > 125) + return 126 | m; + else + return cast(ubyte)this.payload.length | m; + } + + /// Ditto + private ushort shortPayloadLength() const pure nothrow @safe @nogc + { + assert(this.payload.length >= 126, + "ushort version shouldn't be called when payload < 126"); + assert(this.payload.length <= ushort.max, + "ushort version shouldn't be called when payload > ushort.max"); + return cast(ushort)this.payload.length; + } + + /// Ditto + private ulong longPayloadLength() const pure nothrow @safe @nogc + { + assert(this.payload.length > ushort.max, + "ulong version shouldn't be called when payload <= ushort.max"); + return this.payload.length; + } + + + /** + * Write a single Websocket frame to the output stream + * + * Params: + * stream = The connection to write to. Required parameter. + * sys_rng = The source of entropy to use. + * If this parameter is provided, it is assumed + * that this frame should be masked (mandatory for + * client frame), in which case the payload will + * be XOR-ed using a 32 bits value. + * Default to null (client frame). + * + * See_Also: + * https://tools.ietf.org/html/rfc6455#section-5.2 + * for a broad overview. + */ + void writeFrame(OutputStream stream, SystemRNG sys_rng = null) { + + import std.bitmanip : nativeToBigEndian; import vibe.stream.wrapper; auto rng = StreamOutputRange(stream); - ubyte[4] buff; - ubyte firstByte = cast(ubyte)opcode; - if (fin) firstByte |= 0x80; - rng.put(firstByte); + + // The first byte of the header + rng.put(this.fin_rsv_opcode); auto b1 = 0; if (sys_rng) { b1 = 0x80; } - if( payload.length < 126 ) { - rng.put(std.bitmanip.nativeToBigEndian(cast(ubyte)(b1 | payload.length))); - } else if( payload.length <= 65536 ) { - buff[0] = cast(ubyte) (b1 | 126); - rng.put(buff[0 .. 1]); - rng.put(std.bitmanip.nativeToBigEndian(cast(ushort)payload.length)); - } else { - buff[0] = cast(ubyte) (b1 | 127); - rng.put(buff[0 .. 1]); - rng.put(std.bitmanip.nativeToBigEndian(payload.length)); - } + // Writing the basic payload length + rng.put(nativeToBigEndian(this.smallPayloadLength(!!sys_rng))); + if (payload.length < 126) {} + else if (payload.length <= ushort.max) + rng.put(nativeToBigEndian(this.shortPayloadLength)); + else + rng.put(nativeToBigEndian(this.longPayloadLength)); - if (sys_rng) { + if (sys_rng) { // Client Frame sys_rng.read(buff); rng.put(buff); for (size_t i = 0; i < payload.length; i++) { payload[i] ^= buff[i % 4]; } rng.put(payload); - }else { + } else { // Server frame rng.put(payload); } + rng.flush(); stream.flush(); } From 0fc83fc117035130f0bd85817e5d9ae51710748e Mon Sep 17 00:00:00 2001 From: Geod24 Date: Sun, 17 Jul 2016 23:41:17 +0200 Subject: [PATCH 10/10] WebSocket: Improvements to Frame.readFrame Reduce number of static buffers (3 -> 1) Comment the internals Only demask when the frame is masked (the previous code didn't yield incorrect data, but needlessly iterated and assigned data to an array) Check that the most significant bit of the 8 bytes length is 0. --- http/vibe/http/websockets.d | 52 +++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/http/vibe/http/websockets.d b/http/vibe/http/websockets.d index 195e68bfb2..c62f52fecb 100644 --- a/http/vibe/http/websockets.d +++ b/http/vibe/http/websockets.d @@ -918,22 +918,28 @@ struct Frame { static Frame readFrame(InputStream stream) { Frame frame; - ubyte[2] data2; - ubyte[8] data8; - stream.read(data2); - //enforceEx!WebSocketException( (data[0] & 0x70) != 0, "reserved bits must be unset" ); - frame.fin = (data2[0] & 0x80) == 0x80; - bool masked = (data2[1] & 0x80) == 0x80; - frame.opcode = cast(FrameOpcode)(data2[0] & 0xf); + ubyte[8] data; + + stream.read(data[0 .. 2]); + frame.fin_rsv_opcode = data[0]; + + bool masked = !!(data[1] & 0b1000_0000); //parsing length - ulong length = data2[1] & 0x7f; - if( length == 126 ) { - stream.read(data2); - length = bigEndianToNative!ushort(data2); - } else if( length == 127 ) { - stream.read(data8); - length = bigEndianToNative!ulong(data8); + ulong length = data[1] & 0b0111_1111; + if (length == 126) { + stream.read(data[0 .. 2]); + length = bigEndianToNative!ushort(data[0 .. 2]); + } else if (length == 127) { + stream.read(data); + length = bigEndianToNative!ulong(data); + + // RFC 6455, 5.2, 'Payload length': If 127, the following 8 bytes + // interpreted as a 64-bit unsigned integer (the most significant + // bit MUST be 0) + enforceEx!WebSocketException(!(length >> 63), + "Received length has a non-zero most significant bit"); + } logDebug("Read frame: %s %s %s length=%d", frame.opcode, @@ -941,19 +947,21 @@ struct Frame { masked ? "masked" : "not masked", length); - //masking key - ubyte[4] maskingKey; - if( masked ) stream.read(maskingKey); + // Masking key is 32 bits / uint + if (masked) + stream.read(data[0 .. 4]); - //payload + // Read payload + // TODO: Provide a way to limit the size read, easy + // DOS for server code here (rejectedsoftware/vibe.d#1496). enforceEx!WebSocketException(length <= size_t.max); - frame.payload = new ubyte[cast(size_t)length]; + frame.payload = new ubyte[](cast(size_t)length); stream.read(frame.payload); //de-masking - for( size_t i = 0; i < length; ++i ) { - frame.payload[i] = frame.payload[i] ^ maskingKey[i % 4]; - } + if (masked) + foreach (size_t i; 0 .. cast(size_t)length) + frame.payload[i] = frame.payload[i] ^ data[i % 4]; return frame; }