summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoryutak@chromium.org <yutak@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-05-10 04:21:48 +0000
committeryutak@chromium.org <yutak@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-05-10 04:21:48 +0000
commitcf901f5828eb2bb362548cdf785c5d162ef3d669 (patch)
treeb5c29a243de90e6a0a8f60d97309a291b09ccc87
parentd72d3a6b57d88fe88d3db71dd17239166a1c59ff (diff)
downloadchromium_src-cf901f5828eb2bb362548cdf785c5d162ef3d669.zip
chromium_src-cf901f5828eb2bb362548cdf785c5d162ef3d669.tar.gz
chromium_src-cf901f5828eb2bb362548cdf785c5d162ef3d669.tar.bz2
Add WebSocketFrameParser.
BUG=121052 TEST=none Review URL: https://chromiumcodereview.appspot.com/9956013 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@136240 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--net/net.gyp5
-rw-r--r--net/websockets/websocket_frame.cc24
-rw-r--r--net/websockets/websocket_frame.h80
-rw-r--r--net/websockets/websocket_frame_parser.cc212
-rw-r--r--net/websockets/websocket_frame_parser.h85
-rw-r--r--net/websockets/websocket_frame_parser_unittest.cc518
6 files changed, 924 insertions, 0 deletions
diff --git a/net/net.gyp b/net/net.gyp
index 5120768..bd82c8e 100644
--- a/net/net.gyp
+++ b/net/net.gyp
@@ -766,6 +766,10 @@
'url_request/url_request_throttler_manager.h',
'url_request/view_cache_helper.cc',
'url_request/view_cache_helper.h',
+ 'websockets/websocket_frame.cc',
+ 'websockets/websocket_frame.h',
+ 'websockets/websocket_frame_parser.cc',
+ 'websockets/websocket_frame_parser.h',
'websockets/websocket_handshake_handler.cc',
'websockets/websocket_handshake_handler.h',
'websockets/websocket_job.cc',
@@ -1251,6 +1255,7 @@
'url_request/url_request_throttler_unittest.cc',
'url_request/url_request_unittest.cc',
'url_request/view_cache_helper_unittest.cc',
+ 'websockets/websocket_frame_parser_unittest.cc',
'websockets/websocket_handshake_handler_unittest.cc',
'websockets/websocket_job_spdy2_unittest.cc',
'websockets/websocket_job_spdy3_unittest.cc',
diff --git a/net/websockets/websocket_frame.cc b/net/websockets/websocket_frame.cc
new file mode 100644
index 0000000..5957cef
--- /dev/null
+++ b/net/websockets/websocket_frame.cc
@@ -0,0 +1,24 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/websockets/websocket_frame.h"
+
+namespace net {
+
+// Definitions for in-struct constants.
+const WebSocketFrameHeader::OpCode WebSocketFrameHeader::kOpCodeContinuation =
+ 0x0;
+const WebSocketFrameHeader::OpCode WebSocketFrameHeader::kOpCodeText = 0x1;
+const WebSocketFrameHeader::OpCode WebSocketFrameHeader::kOpCodeBinary = 0x2;
+const WebSocketFrameHeader::OpCode WebSocketFrameHeader::kOpCodeClose = 0x8;
+const WebSocketFrameHeader::OpCode WebSocketFrameHeader::kOpCodePing = 0x9;
+const WebSocketFrameHeader::OpCode WebSocketFrameHeader::kOpCodePong = 0xA;
+
+WebSocketFrameChunk::WebSocketFrameChunk() {
+}
+
+WebSocketFrameChunk::~WebSocketFrameChunk() {
+}
+
+} // namespace net
diff --git a/net/websockets/websocket_frame.h b/net/websockets/websocket_frame.h
new file mode 100644
index 0000000..593d439
--- /dev/null
+++ b/net/websockets/websocket_frame.h
@@ -0,0 +1,80 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_WEBSOCKETS_WEBSOCKET_FRAME_H_
+#define NET_WEBSOCKETS_WEBSOCKET_FRAME_H_
+#pragma once
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+// Represents a WebSocket frame header.
+//
+// Members of this class correspond to each element in WebSocket frame header
+// (see http://tools.ietf.org/html/rfc6455#section-5.2).
+struct NET_EXPORT_PRIVATE WebSocketFrameHeader {
+ typedef int OpCode;
+ static const OpCode kOpCodeContinuation;
+ static const OpCode kOpCodeText;
+ static const OpCode kOpCodeBinary;
+ static const OpCode kOpCodeClose;
+ static const OpCode kOpCodePing;
+ static const OpCode kOpCodePong;
+
+ // These values must be a compile-time constant. "enum hack" is used here
+ // to make MSVC happy.
+ enum {
+ kBaseHeaderSize = 2,
+ kMaximumExtendedLengthSize = 8,
+ kMaskingKeyLength = 4
+ };
+
+ // Members below correspond to each item in WebSocket frame header.
+ // See <http://tools.ietf.org/html/rfc6455#section-5.2> for details.
+ bool final;
+ bool reserved1;
+ bool reserved2;
+ bool reserved3;
+ OpCode opcode;
+ bool masked;
+ uint64 payload_length;
+};
+
+// Contains payload data of part of a WebSocket frame.
+//
+// Payload of a WebSocket frame may be divided into multiple chunks.
+// You need to look at |final_chunk| member variable to detect the end of a
+// series of chunk objects of a WebSocket frame.
+//
+// Frame dissection is necessary to handle WebSocket frame stream containing
+// abritrarily large frames in the browser process. Because the server may send
+// a huge frame that doesn't fit in the memory, we cannot store the entire
+// payload data in the memory.
+//
+// Users of this struct should treat WebSocket frames as a data stream; it's
+// important to keep the frame data flowing, especially in the browser process.
+// Users should not let the data stuck somewhere in the pipeline.
+struct NET_EXPORT_PRIVATE WebSocketFrameChunk {
+ WebSocketFrameChunk();
+ ~WebSocketFrameChunk();
+
+ // Non-null |header| is provided only if this chunk is the first part of
+ // a series of chunks.
+ scoped_ptr<WebSocketFrameHeader> header;
+
+ // Indicates this part is the last chunk of a frame.
+ bool final_chunk;
+
+ // |data| is always unmasked even if the frame is masked.
+ std::vector<char> data;
+};
+
+} // namespace net
+
+#endif // NET_WEBSOCKETS_WEBSOCKET_FRAME_H_
diff --git a/net/websockets/websocket_frame_parser.cc b/net/websockets/websocket_frame_parser.cc
new file mode 100644
index 0000000..1acc64c
--- /dev/null
+++ b/net/websockets/websocket_frame_parser.cc
@@ -0,0 +1,212 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/websockets/websocket_frame_parser.h"
+
+#include <algorithm>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "net/base/big_endian.h"
+#include "net/websockets/websocket_frame.h"
+
+namespace {
+
+const uint8 kFinalBit = 0x80;
+const uint8 kReserved1Bit = 0x40;
+const uint8 kReserved2Bit = 0x20;
+const uint8 kReserved3Bit = 0x10;
+const uint8 kOpCodeMask = 0xF;
+const uint8 kMaskBit = 0x80;
+const uint8 kPayloadLengthMask = 0x7F;
+const uint64 kMaxPayloadLengthWithoutExtendedLengthField = 125;
+const uint64 kPayloadLengthWithTwoByteExtendedLengthField = 126;
+const uint64 kPayloadLengthWithEightByteExtendedLengthField = 127;
+
+} // Unnamed namespace.
+
+namespace net {
+
+WebSocketFrameParser::WebSocketFrameParser()
+ : current_read_pos_(0),
+ frame_offset_(0),
+ failed_(false) {
+ std::fill(masking_key_,
+ masking_key_ + WebSocketFrameHeader::kMaskingKeyLength,
+ '\0');
+}
+
+WebSocketFrameParser::~WebSocketFrameParser() {
+}
+
+bool WebSocketFrameParser::Decode(
+ const char* data,
+ size_t length,
+ ScopedVector<WebSocketFrameChunk>* frame_chunks) {
+ if (failed_)
+ return false;
+ if (!length)
+ return true;
+
+ // TODO(yutak): Remove copy.
+ buffer_.insert(buffer_.end(), data, data + length);
+
+ while (current_read_pos_ < buffer_.size()) {
+ bool first_chunk = false;
+ if (!current_frame_header_.get()) {
+ DecodeFrameHeader();
+ if (failed_)
+ return false;
+ // If frame header is incomplete, then carry over the remaining
+ // data to the next round of Decode().
+ if (!current_frame_header_.get())
+ break;
+ first_chunk = true;
+ }
+
+ scoped_ptr<WebSocketFrameChunk> frame_chunk =
+ DecodeFramePayload(first_chunk);
+ DCHECK(frame_chunk.get());
+ frame_chunks->push_back(frame_chunk.release());
+
+ if (current_frame_header_.get()) {
+ DCHECK(current_read_pos_ == buffer_.size());
+ break;
+ }
+ }
+
+ // Drain unnecessary data. TODO(yutak): Remove copy. (but how?)
+ buffer_.erase(buffer_.begin(), buffer_.begin() + current_read_pos_);
+ current_read_pos_ = 0;
+
+ // Sanity check: the size of carried-over data should not exceed
+ // the maximum possible length of a frame header.
+ static const size_t kMaximumFrameHeaderSize =
+ WebSocketFrameHeader::kBaseHeaderSize +
+ WebSocketFrameHeader::kMaximumExtendedLengthSize +
+ WebSocketFrameHeader::kMaskingKeyLength;
+ DCHECK_LT(buffer_.size(), kMaximumFrameHeaderSize);
+
+ return true;
+}
+
+void WebSocketFrameParser::DecodeFrameHeader() {
+ typedef WebSocketFrameHeader::OpCode OpCode;
+ static const int kMaskingKeyLength = WebSocketFrameHeader::kMaskingKeyLength;
+
+ DCHECK(!current_frame_header_.get());
+
+ const char* start = &buffer_.front() + current_read_pos_;
+ const char* current = start;
+ const char* end = &buffer_.front() + buffer_.size();
+
+ // Header needs 2 bytes at minimum.
+ if (end - current < 2)
+ return;
+
+ uint8 first_byte = *current++;
+ uint8 second_byte = *current++;
+
+ bool final = (first_byte & kFinalBit) != 0;
+ bool reserved1 = (first_byte & kReserved1Bit) != 0;
+ bool reserved2 = (first_byte & kReserved2Bit) != 0;
+ bool reserved3 = (first_byte & kReserved3Bit) != 0;
+ OpCode opcode = first_byte & kOpCodeMask;
+
+ bool masked = (second_byte & kMaskBit) != 0;
+ uint64 payload_length = second_byte & kPayloadLengthMask;
+ bool valid_length_format = true;
+ if (payload_length == kPayloadLengthWithTwoByteExtendedLengthField) {
+ if (end - current < 2)
+ return;
+ uint16 payload_length_16;
+ ReadBigEndian(current, &payload_length_16);
+ current += 2;
+ payload_length = payload_length_16;
+ if (payload_length <= kMaxPayloadLengthWithoutExtendedLengthField)
+ valid_length_format = false;
+ } else if (payload_length == kPayloadLengthWithEightByteExtendedLengthField) {
+ if (end - current < 8)
+ return;
+ ReadBigEndian(current, &payload_length);
+ current += 8;
+ if (payload_length <= kuint16max ||
+ payload_length > static_cast<uint64>(kint64max)) {
+ valid_length_format = false;
+ }
+ }
+ if (!valid_length_format) {
+ failed_ = true;
+ buffer_.clear();
+ current_read_pos_ = 0;
+ current_frame_header_.reset();
+ frame_offset_ = 0;
+ return;
+ }
+
+ if (masked) {
+ if (end - current < kMaskingKeyLength)
+ return;
+ std::copy(current, current + kMaskingKeyLength, masking_key_);
+ current += kMaskingKeyLength;
+ } else {
+ std::fill(masking_key_, masking_key_ + kMaskingKeyLength, '\0');
+ }
+
+ current_frame_header_.reset(new WebSocketFrameHeader);
+ current_frame_header_->final = final;
+ current_frame_header_->reserved1 = reserved1;
+ current_frame_header_->reserved2 = reserved2;
+ current_frame_header_->reserved3 = reserved3;
+ current_frame_header_->opcode = opcode;
+ current_frame_header_->masked = masked;
+ current_frame_header_->payload_length = payload_length;
+ current_read_pos_ += current - start;
+ DCHECK_EQ(0u, frame_offset_);
+}
+
+scoped_ptr<WebSocketFrameChunk> WebSocketFrameParser::DecodeFramePayload(
+ bool first_chunk) {
+ static const int kMaskingKeyLength = WebSocketFrameHeader::kMaskingKeyLength;
+
+ const char* current = &buffer_.front() + current_read_pos_;
+ const char* end = &buffer_.front() + buffer_.size();
+ uint64 next_size = std::min<uint64>(
+ end - current,
+ current_frame_header_->payload_length - frame_offset_);
+
+ scoped_ptr<WebSocketFrameChunk> frame_chunk(new WebSocketFrameChunk);
+ if (first_chunk) {
+ frame_chunk->header.reset(new WebSocketFrameHeader(*current_frame_header_));
+ }
+ frame_chunk->final_chunk = false;
+ frame_chunk->data.assign(current, current + next_size);
+ if (current_frame_header_->masked) {
+ // Unmask the payload.
+ // TODO(yutak): This could be faster by doing unmasking for each
+ // machine word (instead of each byte).
+ size_t key_offset = frame_offset_ % kMaskingKeyLength;
+ for (uint64 i = 0; i < next_size; ++i) {
+ frame_chunk->data[i] ^= masking_key_[key_offset];
+ key_offset = (key_offset + 1) % kMaskingKeyLength;
+ }
+ }
+
+ current_read_pos_ += next_size;
+ frame_offset_ += next_size;
+
+ DCHECK_LE(frame_offset_, current_frame_header_->payload_length);
+ if (frame_offset_ == current_frame_header_->payload_length) {
+ frame_chunk->final_chunk = true;
+ current_frame_header_.reset();
+ frame_offset_ = 0;
+ }
+
+ return frame_chunk.Pass();
+}
+
+} // namespace net
diff --git a/net/websockets/websocket_frame_parser.h b/net/websockets/websocket_frame_parser.h
new file mode 100644
index 0000000..3becb81
--- /dev/null
+++ b/net/websockets/websocket_frame_parser.h
@@ -0,0 +1,85 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_WEBSOCKETS_WEBSOCKET_FRAME_PARSER_H_
+#define NET_WEBSOCKETS_WEBSOCKET_FRAME_PARSER_H_
+#pragma once
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "net/base/net_export.h"
+#include "net/websockets/websocket_frame.h"
+
+namespace net {
+
+// Parses WebSocket frames from byte stream.
+//
+// Specification of WebSocket frame format is available at
+// <http://tools.ietf.org/html/rfc6455#section-5>.
+
+class NET_EXPORT_PRIVATE WebSocketFrameParser {
+ public:
+ WebSocketFrameParser();
+ ~WebSocketFrameParser();
+
+ // Decodes the given byte stream and stores parsed WebSocket frames in
+ // |frames|.
+ //
+ // If the parser encounters invalid payload length format, Decode() fails
+ // and returns false. Once Decode() has failed, the parser refuses to decode
+ // any more data and future invocations of Decode() will simply return false.
+ //
+ // Payload data of parsed WebSocket frames may be incomplete; see comments in
+ // websocket_frame.h for more details.
+ bool Decode(const char* data,
+ size_t length,
+ ScopedVector<WebSocketFrameChunk>* frame_chunks);
+
+ // Returns true if the parser has ever failed to decode a WebSocket frame.
+ // TODO(yutak): Provide human-readable description of failure.
+ bool failed() const { return failed_; }
+
+ private:
+ // Tries to decode a frame header from |current_read_pos_|.
+ // If successful, this function updates |current_read_pos_|,
+ // |current_frame_header_|, and |masking_key_| (if available).
+ // This function may set |failed_| to true if it observes a corrupt frame.
+ // If there is not enough data in the remaining buffer to parse a frame
+ // header, this function returns without doing anything.
+ void DecodeFrameHeader();
+
+ // Decodes frame payload and creates a WebSocketFrameChunk object.
+ // This function updates |current_read_pos_| and |frame_offset_| after
+ // parsing. This function returns a frame object even if no payload data is
+ // available at this moment, so the receiver could make use of frame header
+ // information. If the end of frame is reached, this function clears
+ // |current_frame_header_|, |frame_offset_| and |masking_key_|.
+ scoped_ptr<WebSocketFrameChunk> DecodeFramePayload(bool first_chunk);
+
+ // Internal buffer to store the data to parse.
+ std::vector<char> buffer_;
+
+ // Position in |buffer_| where the next round of parsing starts.
+ size_t current_read_pos_;
+
+ // Frame header and masking key of the current frame.
+ // |masking_key_| is filled with zeros if the current frame is not masked.
+ scoped_ptr<WebSocketFrameHeader> current_frame_header_;
+ char masking_key_[WebSocketFrameHeader::kMaskingKeyLength];
+
+ // Amount of payload data read so far for the current frame.
+ uint64 frame_offset_;
+
+ bool failed_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebSocketFrameParser);
+};
+
+} // namespace net
+
+#endif // NET_WEBSOCKETS_WEBSOCKET_FRAME_PARSER_H_
diff --git a/net/websockets/websocket_frame_parser_unittest.cc b/net/websockets/websocket_frame_parser_unittest.cc
new file mode 100644
index 0000000..9ddffbd
--- /dev/null
+++ b/net/websockets/websocket_frame_parser_unittest.cc
@@ -0,0 +1,518 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/websockets/websocket_frame_parser.h"
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_vector.h"
+#include "base/port.h"
+#include "net/websockets/websocket_frame.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+const char kHello[] = "Hello, world!";
+const uint64 kHelloLength = arraysize(kHello) - 1;
+const char kHelloFrame[] = "\x81\x0DHello, world!";
+const uint64 kHelloFrameLength = arraysize(kHelloFrame) - 1;
+const char kMaskedHelloFrame[] =
+ "\x81\x8D\xDE\xAD\xBE\xEF"
+ "\x96\xC8\xD2\x83\xB1\x81\x9E\x98\xB1\xDF\xD2\x8B\xFF";
+const uint64 kMaskedHelloFrameLength = arraysize(kMaskedHelloFrame) - 1;
+
+struct FrameHeaderTestCase {
+ const char* frame_header;
+ size_t frame_header_length;
+ uint64 frame_length;
+};
+
+const FrameHeaderTestCase kFrameHeaderTests[] = {
+ { "\x81\x00", 2, GG_UINT64_C(0) },
+ { "\x81\x7D", 2, GG_UINT64_C(125) },
+ { "\x81\x7E\x00\x7E", 4, GG_UINT64_C(126) },
+ { "\x81\x7E\xFF\xFF", 4, GG_UINT64_C(0xFFFF) },
+ { "\x81\x7F\x00\x00\x00\x00\x00\x01\x00\x00", 10, GG_UINT64_C(0x10000) },
+ { "\x81\x7F\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 10,
+ GG_UINT64_C(0x7FFFFFFFFFFFFFFF) }
+};
+const int kNumFrameHeaderTests = arraysize(kFrameHeaderTests);
+
+} // Unnamed namespace
+
+namespace net {
+
+TEST(WebSocketFrameParserTest, DecodeNormalFrame) {
+ WebSocketFrameParser parser;
+
+ ScopedVector<WebSocketFrameChunk> frames;
+ EXPECT_TRUE(parser.Decode(kHelloFrame, kHelloFrameLength, &frames));
+ EXPECT_FALSE(parser.failed());
+ ASSERT_EQ(1u, frames.size());
+ WebSocketFrameChunk* frame = frames[0];
+ ASSERT_TRUE(frame != NULL);
+ const WebSocketFrameHeader* header = frame->header.get();
+ EXPECT_TRUE(header != NULL);
+ if (header) {
+ EXPECT_TRUE(header->final);
+ EXPECT_FALSE(header->reserved1);
+ EXPECT_FALSE(header->reserved2);
+ EXPECT_FALSE(header->reserved3);
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, header->opcode);
+ EXPECT_FALSE(header->masked);
+ EXPECT_EQ(kHelloLength, header->payload_length);
+ }
+ EXPECT_TRUE(frame->final_chunk);
+
+ std::vector<char> expected_data(kHello, kHello + kHelloLength);
+ EXPECT_EQ(expected_data, frame->data);
+}
+
+TEST(WebSocketFrameParserTest, DecodeMaskedFrame) {
+ WebSocketFrameParser parser;
+
+ ScopedVector<WebSocketFrameChunk> frames;
+ EXPECT_TRUE(parser.Decode(kMaskedHelloFrame, kMaskedHelloFrameLength,
+ &frames));
+ EXPECT_FALSE(parser.failed());
+ ASSERT_EQ(1u, frames.size());
+ WebSocketFrameChunk* frame = frames[0];
+ ASSERT_TRUE(frame != NULL);
+ const WebSocketFrameHeader* header = frame->header.get();
+ EXPECT_TRUE(header != NULL);
+ if (header) {
+ EXPECT_TRUE(header->final);
+ EXPECT_FALSE(header->reserved1);
+ EXPECT_FALSE(header->reserved2);
+ EXPECT_FALSE(header->reserved3);
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, header->opcode);
+ EXPECT_TRUE(header->masked);
+ EXPECT_EQ(kHelloLength, header->payload_length);
+ }
+ EXPECT_TRUE(frame->final_chunk);
+
+ std::vector<char> expected_data(kHello, kHello + kHelloLength);
+ EXPECT_EQ(expected_data, frame->data);
+}
+
+TEST(WebSocketFrameParserTest, DecodeManyFrames) {
+ struct Input {
+ const char* frame;
+ size_t frame_length;
+ const char* expected_payload;
+ size_t expected_payload_length;
+ };
+ static const Input kInputs[] = {
+ // Each |frame| data is split into two string literals because C++ lexers
+ // consume unlimited number of hex characters in a hex character escape
+ // (e.g. "\x05F" is not treated as { '\x5', 'F', '\0' } but as
+ // { '\x5F', '\0' }).
+ { "\x81\x05" "First", 7, "First", 5 },
+ { "\x81\x06" "Second", 8, "Second", 6 },
+ { "\x81\x05" "Third", 7, "Third", 5 },
+ { "\x81\x06" "Fourth", 8, "Fourth", 6 },
+ { "\x81\x05" "Fifth", 7, "Fifth", 5 },
+ { "\x81\x05" "Sixth", 7, "Sixth", 5 },
+ { "\x81\x07" "Seventh", 9, "Seventh", 7 },
+ { "\x81\x06" "Eighth", 8, "Eighth", 6 },
+ { "\x81\x05" "Ninth", 7, "Ninth", 5 },
+ { "\x81\x05" "Tenth", 7, "Tenth", 5 }
+ };
+ static const int kNumInputs = ARRAYSIZE_UNSAFE(kInputs);
+
+ std::vector<char> input;
+ // Concatenate all frames.
+ for (int i = 0; i < kNumInputs; ++i) {
+ input.insert(input.end(),
+ kInputs[i].frame,
+ kInputs[i].frame + kInputs[i].frame_length);
+ }
+
+ WebSocketFrameParser parser;
+
+ ScopedVector<WebSocketFrameChunk> frames;
+ EXPECT_TRUE(parser.Decode(&input.front(), input.size(), &frames));
+ EXPECT_FALSE(parser.failed());
+ ASSERT_EQ(static_cast<size_t>(kNumInputs), frames.size());
+
+ for (int i = 0; i < kNumInputs; ++i) {
+ WebSocketFrameChunk* frame = frames[i];
+ EXPECT_TRUE(frame != NULL);
+ if (!frame)
+ continue;
+ EXPECT_TRUE(frame->final_chunk);
+ std::vector<char> expected_data(
+ kInputs[i].expected_payload,
+ kInputs[i].expected_payload + kInputs[i].expected_payload_length);
+ EXPECT_EQ(expected_data, frame->data);
+
+ const WebSocketFrameHeader* header = frame->header.get();
+ EXPECT_TRUE(header != NULL);
+ if (!header)
+ continue;
+ EXPECT_TRUE(header->final);
+ EXPECT_FALSE(header->reserved1);
+ EXPECT_FALSE(header->reserved2);
+ EXPECT_FALSE(header->reserved3);
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, header->opcode);
+ EXPECT_FALSE(header->masked);
+ EXPECT_EQ(kInputs[i].expected_payload_length, header->payload_length);
+ }
+}
+
+TEST(WebSocketFrameParserTest, DecodePartialFrame) {
+ static const size_t kFrameHeaderSize = 2;
+
+ for (size_t cutting_pos = 0; cutting_pos < kHelloLength; ++cutting_pos) {
+ std::vector<char> input1(kHelloFrame,
+ kHelloFrame + kFrameHeaderSize + cutting_pos);
+ std::vector<char> input2(kHelloFrame + input1.size(),
+ kHelloFrame + kHelloFrameLength);
+
+ std::vector<char> expected1(kHello, kHello + cutting_pos);
+ std::vector<char> expected2(kHello + cutting_pos, kHello + kHelloLength);
+
+ WebSocketFrameParser parser;
+
+ ScopedVector<WebSocketFrameChunk> frames1;
+ EXPECT_TRUE(parser.Decode(&input1.front(), input1.size(), &frames1));
+ EXPECT_FALSE(parser.failed());
+ EXPECT_EQ(1u, frames1.size());
+ if (frames1.size() != 1u)
+ continue;
+ WebSocketFrameChunk* frame1 = frames1[0];
+ EXPECT_TRUE(frame1 != NULL);
+ if (!frame1)
+ continue;
+ EXPECT_FALSE(frame1->final_chunk);
+ EXPECT_EQ(expected1, frame1->data);
+ const WebSocketFrameHeader* header1 = frame1->header.get();
+ EXPECT_TRUE(header1 != NULL);
+ if (!header1)
+ continue;
+ EXPECT_TRUE(header1->final);
+ EXPECT_FALSE(header1->reserved1);
+ EXPECT_FALSE(header1->reserved2);
+ EXPECT_FALSE(header1->reserved3);
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, header1->opcode);
+ EXPECT_FALSE(header1->masked);
+ EXPECT_EQ(kHelloLength, header1->payload_length);
+
+ ScopedVector<WebSocketFrameChunk> frames2;
+ EXPECT_TRUE(parser.Decode(&input2.front(), input2.size(), &frames2));
+ EXPECT_FALSE(parser.failed());
+ EXPECT_EQ(1u, frames2.size());
+ if (frames2.size() != 1u)
+ continue;
+ WebSocketFrameChunk* frame2 = frames2[0];
+ EXPECT_TRUE(frame2 != NULL);
+ if (!frame2)
+ continue;
+ EXPECT_TRUE(frame2->final_chunk);
+ EXPECT_EQ(expected2, frame2->data);
+ const WebSocketFrameHeader* header2 = frame2->header.get();
+ EXPECT_TRUE(header2 == NULL);
+ }
+}
+
+TEST(WebSocketFrameParserTest, DecodePartialMaskedFrame) {
+ static const size_t kFrameHeaderSize = 6;
+
+ for (size_t cutting_pos = 0; cutting_pos < kHelloLength; ++cutting_pos) {
+ std::vector<char> input1(
+ kMaskedHelloFrame,
+ kMaskedHelloFrame + kFrameHeaderSize + cutting_pos);
+ std::vector<char> input2(kMaskedHelloFrame + input1.size(),
+ kMaskedHelloFrame + kMaskedHelloFrameLength);
+
+ std::vector<char> expected1(kHello, kHello + cutting_pos);
+ std::vector<char> expected2(kHello + cutting_pos, kHello + kHelloLength);
+
+ WebSocketFrameParser parser;
+
+ ScopedVector<WebSocketFrameChunk> frames1;
+ EXPECT_TRUE(parser.Decode(&input1.front(), input1.size(), &frames1));
+ EXPECT_FALSE(parser.failed());
+ EXPECT_EQ(1u, frames1.size());
+ if (frames1.size() != 1u)
+ continue;
+ WebSocketFrameChunk* frame1 = frames1[0];
+ EXPECT_TRUE(frame1 != NULL);
+ if (!frame1)
+ continue;
+ EXPECT_FALSE(frame1->final_chunk);
+ EXPECT_EQ(expected1, frame1->data);
+ const WebSocketFrameHeader* header1 = frame1->header.get();
+ EXPECT_TRUE(header1 != NULL);
+ if (!header1)
+ continue;
+ EXPECT_TRUE(header1->final);
+ EXPECT_FALSE(header1->reserved1);
+ EXPECT_FALSE(header1->reserved2);
+ EXPECT_FALSE(header1->reserved3);
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, header1->opcode);
+ EXPECT_TRUE(header1->masked);
+ EXPECT_EQ(kHelloLength, header1->payload_length);
+
+ ScopedVector<WebSocketFrameChunk> frames2;
+ EXPECT_TRUE(parser.Decode(&input2.front(), input2.size(), &frames2));
+ EXPECT_FALSE(parser.failed());
+ EXPECT_EQ(1u, frames2.size());
+ if (frames2.size() != 1u)
+ continue;
+ WebSocketFrameChunk* frame2 = frames2[0];
+ EXPECT_TRUE(frame2 != NULL);
+ if (!frame2)
+ continue;
+ EXPECT_TRUE(frame2->final_chunk);
+ EXPECT_EQ(expected2, frame2->data);
+ const WebSocketFrameHeader* header2 = frame2->header.get();
+ EXPECT_TRUE(header2 == NULL);
+ }
+}
+
+TEST(WebSocketFrameParserTest, DecodeFramesOfVariousLengths) {
+ for (int i = 0; i < kNumFrameHeaderTests; ++i) {
+ const char* frame_header = kFrameHeaderTests[i].frame_header;
+ size_t frame_header_length = kFrameHeaderTests[i].frame_header_length;
+ uint64 frame_length = kFrameHeaderTests[i].frame_length;
+
+ std::vector<char> input(frame_header, frame_header + frame_header_length);
+ // Limit the payload size not to flood the console on failure.
+ static const uint64 kMaxPayloadSize = 200;
+ uint64 input_payload_size = std::min(frame_length, kMaxPayloadSize);
+ input.insert(input.end(), input_payload_size, 'a');
+
+ WebSocketFrameParser parser;
+
+ ScopedVector<WebSocketFrameChunk> frames;
+ EXPECT_TRUE(parser.Decode(&input.front(), input.size(), &frames));
+ EXPECT_FALSE(parser.failed());
+ EXPECT_EQ(1u, frames.size());
+ if (frames.size() != 1u)
+ continue;
+ WebSocketFrameChunk* frame = frames[0];
+ EXPECT_TRUE(frame != NULL);
+ if (!frame)
+ continue;
+ if (frame_length == input_payload_size)
+ EXPECT_TRUE(frame->final_chunk);
+ else
+ EXPECT_FALSE(frame->final_chunk);
+ std::vector<char> expected_payload(input_payload_size, 'a');
+ EXPECT_EQ(expected_payload, frame->data);
+ const WebSocketFrameHeader* header = frame->header.get();
+ EXPECT_TRUE(header != NULL);
+ if (!header)
+ continue;
+ EXPECT_TRUE(header->final);
+ EXPECT_FALSE(header->reserved1);
+ EXPECT_FALSE(header->reserved2);
+ EXPECT_FALSE(header->reserved3);
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, header->opcode);
+ EXPECT_FALSE(header->masked);
+ EXPECT_EQ(frame_length, header->payload_length);
+ }
+}
+
+TEST(WebSocketFrameParserTest, DecodePartialHeader) {
+ for (int i = 0; i < kNumFrameHeaderTests; ++i) {
+ const char* frame_header = kFrameHeaderTests[i].frame_header;
+ size_t frame_header_length = kFrameHeaderTests[i].frame_header_length;
+ uint64 frame_length = kFrameHeaderTests[i].frame_length;
+
+ WebSocketFrameParser parser;
+
+ ScopedVector<WebSocketFrameChunk> frames;
+ // Feed each byte to the parser to see if the parser behaves correctly
+ // when it receives partial frame header.
+ for (size_t j = 0; j < frame_header_length; ++j) {
+ EXPECT_TRUE(parser.Decode(frame_header + j, 1, &frames));
+ EXPECT_FALSE(parser.failed());
+ if (j == frame_header_length - 1)
+ EXPECT_EQ(1u, frames.size());
+ else
+ EXPECT_EQ(0u, frames.size());
+ }
+ if (frames.size() != 1u)
+ continue;
+ WebSocketFrameChunk* frame = frames[0];
+ EXPECT_TRUE(frame != NULL);
+ if (!frame)
+ continue;
+ if (frame_length == 0u)
+ EXPECT_TRUE(frame->final_chunk);
+ else
+ EXPECT_FALSE(frame->final_chunk);
+ EXPECT_EQ(std::vector<char>(), frame->data);
+ const WebSocketFrameHeader* header = frame->header.get();
+ EXPECT_TRUE(header != NULL);
+ if (!header)
+ continue;
+ EXPECT_TRUE(header->final);
+ EXPECT_FALSE(header->reserved1);
+ EXPECT_FALSE(header->reserved2);
+ EXPECT_FALSE(header->reserved3);
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, header->opcode);
+ EXPECT_FALSE(header->masked);
+ EXPECT_EQ(frame_length, header->payload_length);
+ }
+}
+
+TEST(WebSocketFrameParserTest, InvalidLengthEncoding) {
+ struct TestCase {
+ const char* frame_header;
+ size_t frame_header_length;
+ };
+ static const TestCase kTests[] = {
+ // For frames with two-byte extended length field, the payload length
+ // should be 126 (0x7E) bytes or more.
+ { "\x81\x7E\x00\x00", 4 },
+ { "\x81\x7E\x00\x7D", 4 },
+ // For frames with eight-byte extended length field, the payload length
+ // should be 0x10000 bytes or more.
+ { "\x81\x7F\x00\x00\x00\x00\x00\x00\x00\x00", 10 },
+ { "\x81\x7E\x00\x00\x00\x00\x00\x00\xFF\xFF", 10 },
+ };
+ static const int kNumTests = ARRAYSIZE_UNSAFE(kTests);
+
+ for (int i = 0; i < kNumTests; ++i) {
+ const char* frame_header = kTests[i].frame_header;
+ size_t frame_header_length = kTests[i].frame_header_length;
+
+ WebSocketFrameParser parser;
+
+ ScopedVector<WebSocketFrameChunk> frames;
+ EXPECT_FALSE(parser.failed());
+ EXPECT_FALSE(parser.Decode(frame_header, frame_header_length, &frames));
+ EXPECT_TRUE(parser.failed());
+ EXPECT_EQ(0u, frames.size());
+
+ // Once the parser has failed, it no longer accepts any input (even if
+ // the input is empty).
+ EXPECT_FALSE(parser.Decode("", 0, &frames));
+ EXPECT_TRUE(parser.failed());
+ EXPECT_EQ(0u, frames.size());
+ }
+}
+
+TEST(WebSocketFrameParserTest, FrameTypes) {
+ struct TestCase {
+ const char* frame_header;
+ size_t frame_header_length;
+ WebSocketFrameHeader::OpCode opcode;
+ };
+ static const TestCase kTests[] = {
+ { "\x80\x00", 2, WebSocketFrameHeader::kOpCodeContinuation },
+ { "\x81\x00", 2, WebSocketFrameHeader::kOpCodeText },
+ { "\x82\x00", 2, WebSocketFrameHeader::kOpCodeBinary },
+ { "\x88\x00", 2, WebSocketFrameHeader::kOpCodeClose },
+ { "\x89\x00", 2, WebSocketFrameHeader::kOpCodePing },
+ { "\x8A\x00", 2, WebSocketFrameHeader::kOpCodePong },
+ // These are undefined opcodes, but the parser needs to be able to parse
+ // them anyway.
+ { "\x83\x00", 2, 0x3 },
+ { "\x84\x00", 2, 0x4 },
+ { "\x85\x00", 2, 0x5 },
+ { "\x86\x00", 2, 0x6 },
+ { "\x87\x00", 2, 0x7 },
+ { "\x8B\x00", 2, 0xB },
+ { "\x8C\x00", 2, 0xC },
+ { "\x8D\x00", 2, 0xD },
+ { "\x8E\x00", 2, 0xE },
+ { "\x8F\x00", 2, 0xF }
+ };
+ static const int kNumTests = ARRAYSIZE_UNSAFE(kTests);
+
+ for (int i = 0; i < kNumTests; ++i) {
+ const char* frame_header = kTests[i].frame_header;
+ size_t frame_header_length = kTests[i].frame_header_length;
+ WebSocketFrameHeader::OpCode opcode = kTests[i].opcode;
+
+ WebSocketFrameParser parser;
+
+ ScopedVector<WebSocketFrameChunk> frames;
+ EXPECT_TRUE(parser.Decode(frame_header, frame_header_length, &frames));
+ EXPECT_FALSE(parser.failed());
+ EXPECT_EQ(1u, frames.size());
+ if (frames.size() != 1u)
+ continue;
+ WebSocketFrameChunk* frame = frames[0];
+ EXPECT_TRUE(frame != NULL);
+ if (!frame)
+ continue;
+ EXPECT_TRUE(frame->final_chunk);
+ EXPECT_EQ(std::vector<char>(), frame->data);
+ const WebSocketFrameHeader* header = frame->header.get();
+ EXPECT_TRUE(header != NULL);
+ if (!header)
+ continue;
+ EXPECT_TRUE(header->final);
+ EXPECT_FALSE(header->reserved1);
+ EXPECT_FALSE(header->reserved2);
+ EXPECT_FALSE(header->reserved3);
+ EXPECT_EQ(opcode, header->opcode);
+ EXPECT_FALSE(header->masked);
+ EXPECT_EQ(0u, header->payload_length);
+ };
+}
+
+TEST(WebSocketFrameParserTest, FinalBitAndReservedBits) {
+ struct TestCase {
+ const char* frame_header;
+ size_t frame_header_length;
+ bool final;
+ bool reserved1;
+ bool reserved2;
+ bool reserved3;
+ };
+ static const TestCase kTests[] = {
+ { "\x81\x00", 2, true, false, false, false },
+ { "\x01\x00", 2, false, false, false, false },
+ { "\xC1\x00", 2, true, true, false, false },
+ { "\xA1\x00", 2, true, false, true, false },
+ { "\x91\x00", 2, true, false, false, true },
+ { "\x71\x00", 2, false, true, true, true },
+ { "\xF1\x00", 2, true, true, true, true }
+ };
+ static const int kNumTests = ARRAYSIZE_UNSAFE(kTests);
+
+ for (int i = 0; i < kNumTests; ++i) {
+ const char* frame_header = kTests[i].frame_header;
+ size_t frame_header_length = kTests[i].frame_header_length;
+ bool final = kTests[i].final;
+ bool reserved1 = kTests[i].reserved1;
+ bool reserved2 = kTests[i].reserved2;
+ bool reserved3 = kTests[i].reserved3;
+
+ WebSocketFrameParser parser;
+
+ ScopedVector<WebSocketFrameChunk> frames;
+ EXPECT_TRUE(parser.Decode(frame_header, frame_header_length, &frames));
+ EXPECT_FALSE(parser.failed());
+ EXPECT_EQ(1u, frames.size());
+ if (frames.size() != 1u)
+ continue;
+ WebSocketFrameChunk* frame = frames[0];
+ EXPECT_TRUE(frame != NULL);
+ if (!frame)
+ continue;
+ EXPECT_TRUE(frame->final_chunk);
+ EXPECT_EQ(std::vector<char>(), frame->data);
+ const WebSocketFrameHeader* header = frame->header.get();
+ EXPECT_TRUE(header != NULL);
+ if (!header)
+ continue;
+ EXPECT_EQ(final, header->final);
+ EXPECT_EQ(reserved1, header->reserved1);
+ EXPECT_EQ(reserved2, header->reserved2);
+ EXPECT_EQ(reserved3, header->reserved3);
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, header->opcode);
+ EXPECT_FALSE(header->masked);
+ EXPECT_EQ(0u, header->payload_length);
+ }
+}
+
+} // namespace net