diff options
-rw-r--r-- | net/net.gyp | 5 | ||||
-rw-r--r-- | net/websockets/websocket_frame.cc | 24 | ||||
-rw-r--r-- | net/websockets/websocket_frame.h | 80 | ||||
-rw-r--r-- | net/websockets/websocket_frame_parser.cc | 212 | ||||
-rw-r--r-- | net/websockets/websocket_frame_parser.h | 85 | ||||
-rw-r--r-- | net/websockets/websocket_frame_parser_unittest.cc | 518 |
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 |