diff options
-rw-r--r-- | net/websockets/websocket_basic_stream.cc | 272 | ||||
-rw-r--r-- | net/websockets/websocket_basic_stream.h | 55 | ||||
-rw-r--r-- | net/websockets/websocket_basic_stream_test.cc | 620 | ||||
-rw-r--r-- | net/websockets/websocket_channel.cc | 237 | ||||
-rw-r--r-- | net/websockets/websocket_channel.h | 49 | ||||
-rw-r--r-- | net/websockets/websocket_channel_test.cc | 893 | ||||
-rw-r--r-- | net/websockets/websocket_frame.cc | 27 | ||||
-rw-r--r-- | net/websockets/websocket_frame.h | 34 | ||||
-rw-r--r-- | net/websockets/websocket_stream.h | 56 |
9 files changed, 1208 insertions, 1035 deletions
diff --git a/net/websockets/websocket_basic_stream.cc b/net/websockets/websocket_basic_stream.cc index 5b02b18..fb8fb0a 100644 --- a/net/websockets/websocket_basic_stream.cc +++ b/net/websockets/websocket_basic_stream.cc @@ -12,6 +12,7 @@ #include "base/basictypes.h" #include "base/bind.h" #include "base/logging.h" +#include "base/safe_numerics.h" #include "net/base/io_buffer.h" #include "net/base/net_errors.h" #include "net/socket/client_socket_handle.h" @@ -23,6 +24,10 @@ namespace net { namespace { +// This uses type uint64 to match the definition of +// WebSocketFrameHeader::payload_length in websocket_frame.h. +const uint64 kMaxControlFramePayload = 125; + // The number of bytes to attempt to read at a time. // TODO(ricea): See if there is a better number or algorithm to fulfill our // requirements: @@ -47,10 +52,9 @@ WebSocketBasicStream::WebSocketBasicStream( WebSocketBasicStream::~WebSocketBasicStream() { Close(); } -int WebSocketBasicStream::ReadFrames( - ScopedVector<WebSocketFrameChunk>* frame_chunks, - const CompletionCallback& callback) { - DCHECK(frame_chunks->empty()); +int WebSocketBasicStream::ReadFrames(ScopedVector<WebSocketFrame>* frames, + const CompletionCallback& callback) { + DCHECK(frames->empty()); // If there is data left over after parsing the HTTP headers, attempt to parse // it as WebSocket frames. if (http_read_buffer_) { @@ -60,80 +64,80 @@ int WebSocketBasicStream::ReadFrames( scoped_refptr<GrowableIOBuffer> buffered_data; buffered_data.swap(http_read_buffer_); DCHECK(http_read_buffer_.get() == NULL); + ScopedVector<WebSocketFrameChunk> frame_chunks; if (!parser_.Decode(buffered_data->StartOfBuffer(), buffered_data->offset(), - frame_chunks)) + &frame_chunks)) return WebSocketErrorToNetError(parser_.websocket_error()); - if (!frame_chunks->empty()) - return OK; + if (!frame_chunks.empty()) { + int result = ConvertChunksToFrames(&frame_chunks, frames); + if (result != ERR_IO_PENDING) + return result; + } } - // Run until socket stops giving us data or we get some chunks. + // Run until socket stops giving us data or we get some frames. while (true) { // base::Unretained(this) here is safe because net::Socket guarantees not to // call any callbacks after Disconnect(), which we call from the - // destructor. The caller of ReadFrames() is required to keep |frame_chunks| + // destructor. The caller of ReadFrames() is required to keep |frames| // valid. - int result = connection_->socket() - ->Read(read_buffer_.get(), - read_buffer_->size(), - base::Bind(&WebSocketBasicStream::OnReadComplete, - base::Unretained(this), - base::Unretained(frame_chunks), - callback)); + int result = connection_->socket()->Read( + read_buffer_.get(), + read_buffer_->size(), + base::Bind(&WebSocketBasicStream::OnReadComplete, + base::Unretained(this), + base::Unretained(frames), + callback)); if (result == ERR_IO_PENDING) return result; - result = HandleReadResult(result, frame_chunks); + result = HandleReadResult(result, frames); if (result != ERR_IO_PENDING) return result; + DCHECK(frames->empty()); } } -int WebSocketBasicStream::WriteFrames( - ScopedVector<WebSocketFrameChunk>* frame_chunks, - const CompletionCallback& callback) { +int WebSocketBasicStream::WriteFrames(ScopedVector<WebSocketFrame>* frames, + const CompletionCallback& callback) { // This function always concatenates all frames into a single buffer. // TODO(ricea): Investigate whether it would be better in some cases to // perform multiple writes with smaller buffers. // // First calculate the size of the buffer we need to allocate. - typedef ScopedVector<WebSocketFrameChunk>::const_iterator Iterator; + typedef ScopedVector<WebSocketFrame>::const_iterator Iterator; const int kMaximumTotalSize = std::numeric_limits<int>::max(); int total_size = 0; - for (Iterator it = frame_chunks->begin(); it != frame_chunks->end(); ++it) { - WebSocketFrameChunk* chunk = *it; - DCHECK(chunk->header) - << "Only complete frames are supported by WebSocketBasicStream"; - DCHECK(chunk->final_chunk) - << "Only complete frames are supported by WebSocketBasicStream"; + for (Iterator it = frames->begin(); it != frames->end(); ++it) { + WebSocketFrame* frame = *it; // Force the masked bit on. - chunk->header->masked = true; + frame->header.masked = true; // We enforce flow control so the renderer should never be able to force us // to cache anywhere near 2GB of frames. - int chunk_size = - chunk->data->size() + GetWebSocketFrameHeaderSize(*(chunk->header)); - CHECK_GE(kMaximumTotalSize - total_size, chunk_size) + int frame_size = frame->header.payload_length + + GetWebSocketFrameHeaderSize(frame->header); + CHECK_GE(kMaximumTotalSize - total_size, frame_size) << "Aborting to prevent overflow"; - total_size += chunk_size; + total_size += frame_size; } scoped_refptr<IOBufferWithSize> combined_buffer( new IOBufferWithSize(total_size)); char* dest = combined_buffer->data(); int remaining_size = total_size; - for (Iterator it = frame_chunks->begin(); it != frame_chunks->end(); ++it) { - WebSocketFrameChunk* chunk = *it; + for (Iterator it = frames->begin(); it != frames->end(); ++it) { + WebSocketFrame* frame = *it; WebSocketMaskingKey mask = generate_websocket_masking_key_(); - int result = WriteWebSocketFrameHeader( - *(chunk->header), &mask, dest, remaining_size); - DCHECK(result != ERR_INVALID_ARGUMENT) + int result = + WriteWebSocketFrameHeader(frame->header, &mask, dest, remaining_size); + DCHECK_NE(ERR_INVALID_ARGUMENT, result) << "WriteWebSocketFrameHeader() says that " << remaining_size << " is not enough to write the header in. This should not happen."; CHECK_GE(result, 0) << "Potentially security-critical check failed"; dest += result; remaining_size -= result; - const char* const frame_data = chunk->data->data(); - const int frame_size = chunk->data->size(); + const char* const frame_data = frame->data->data(); + const int frame_size = frame->header.payload_length; CHECK_GE(remaining_size, frame_size); std::copy(frame_data, frame_data + frame_size, dest); MaskWebSocketFramePayload(mask, 0, dest, frame_size); @@ -196,13 +200,13 @@ int WebSocketBasicStream::WriteEverything( while (buffer->BytesRemaining() > 0) { // The use of base::Unretained() here is safe because on destruction we // disconnect the socket, preventing any further callbacks. - int result = connection_->socket() - ->Write(buffer.get(), - buffer->BytesRemaining(), - base::Bind(&WebSocketBasicStream::OnWriteComplete, - base::Unretained(this), - buffer, - callback)); + int result = connection_->socket()->Write( + buffer.get(), + buffer->BytesRemaining(), + base::Bind(&WebSocketBasicStream::OnWriteComplete, + base::Unretained(this), + buffer, + callback)); if (result > 0) { buffer->DidConsume(result); } else { @@ -217,12 +221,12 @@ void WebSocketBasicStream::OnWriteComplete( const CompletionCallback& callback, int result) { if (result < 0) { - DCHECK(result != ERR_IO_PENDING); + DCHECK_NE(ERR_IO_PENDING, result); callback.Run(result); return; } - DCHECK(result != 0); + DCHECK_NE(0, result); buffer->DidConsume(result); result = WriteEverything(buffer, callback); if (result != ERR_IO_PENDING) @@ -231,27 +235,177 @@ void WebSocketBasicStream::OnWriteComplete( int WebSocketBasicStream::HandleReadResult( int result, - ScopedVector<WebSocketFrameChunk>* frame_chunks) { + ScopedVector<WebSocketFrame>* frames) { DCHECK_NE(ERR_IO_PENDING, result); - DCHECK(frame_chunks->empty()); + DCHECK(frames->empty()); if (result < 0) return result; if (result == 0) return ERR_CONNECTION_CLOSED; - if (!parser_.Decode(read_buffer_->data(), result, frame_chunks)) + ScopedVector<WebSocketFrameChunk> frame_chunks; + if (!parser_.Decode(read_buffer_->data(), result, &frame_chunks)) return WebSocketErrorToNetError(parser_.websocket_error()); - if (!frame_chunks->empty()) - return OK; - return ERR_IO_PENDING; + if (frame_chunks.empty()) + return ERR_IO_PENDING; + return ConvertChunksToFrames(&frame_chunks, frames); } -void WebSocketBasicStream::OnReadComplete( +int WebSocketBasicStream::ConvertChunksToFrames( ScopedVector<WebSocketFrameChunk>* frame_chunks, - const CompletionCallback& callback, - int result) { - result = HandleReadResult(result, frame_chunks); + ScopedVector<WebSocketFrame>* frames) { + for (size_t i = 0; i < frame_chunks->size(); ++i) { + scoped_ptr<WebSocketFrame> frame; + int result = ConvertChunkToFrame( + scoped_ptr<WebSocketFrameChunk>((*frame_chunks)[i]), &frame); + (*frame_chunks)[i] = NULL; + if (result != OK) + return result; + if (frame) + frames->push_back(frame.release()); + } + // All the elements of |frame_chunks| are now NULL, so there is no point in + // calling delete on them all. + frame_chunks->weak_clear(); + if (frames->empty()) + return ERR_IO_PENDING; + return OK; +} + +int WebSocketBasicStream::ConvertChunkToFrame( + scoped_ptr<WebSocketFrameChunk> chunk, + scoped_ptr<WebSocketFrame>* frame) { + DCHECK(frame->get() == NULL); + bool is_first_chunk = false; + if (chunk->header) { + DCHECK(current_frame_header_ == NULL) + << "Received the header for a new frame without notification that " + << "the previous frame was complete (bug in WebSocketFrameParser?)"; + is_first_chunk = true; + current_frame_header_.swap(chunk->header); + } + const int chunk_size = chunk->data ? chunk->data->size() : 0; + DCHECK(current_frame_header_) << "Unexpected header-less chunk received " + << "(final_chunk = " << chunk->final_chunk + << ", data size = " << chunk_size + << ") (bug in WebSocketFrameParser?)"; + scoped_refptr<IOBufferWithSize> data_buffer; + data_buffer.swap(chunk->data); + const bool is_final_chunk = chunk->final_chunk; + const WebSocketFrameHeader::OpCode opcode = current_frame_header_->opcode; + if (WebSocketFrameHeader::IsKnownControlOpCode(opcode)) { + bool protocol_error = false; + if (!current_frame_header_->final) { + DVLOG(1) << "WebSocket protocol error. Control frame, opcode=" << opcode + << " received with FIN bit unset."; + protocol_error = true; + } + if (current_frame_header_->payload_length > kMaxControlFramePayload) { + DVLOG(1) << "WebSocket protocol error. Control frame, opcode=" << opcode + << ", payload_length=" << current_frame_header_->payload_length + << " exceeds maximum payload length for a control message."; + protocol_error = true; + } + if (protocol_error) { + current_frame_header_.reset(); + return ERR_WS_PROTOCOL_ERROR; + } + if (!is_final_chunk) { + DVLOG(2) << "Encountered a split control frame, opcode " << opcode; + if (incomplete_control_frame_body_) { + DVLOG(3) << "Appending to an existing split control frame."; + AddToIncompleteControlFrameBody(data_buffer); + } else { + DVLOG(3) << "Creating new storage for an incomplete control frame."; + incomplete_control_frame_body_ = new GrowableIOBuffer(); + // This method checks for oversize control frames above, so as long as + // the frame parser is working correctly, this won't overflow. If a bug + // does cause it to overflow, it will CHECK() in + // AddToIncompleteControlFrameBody() without writing outside the buffer. + incomplete_control_frame_body_->SetCapacity(kMaxControlFramePayload); + AddToIncompleteControlFrameBody(data_buffer); + } + return OK; + } + if (incomplete_control_frame_body_) { + DVLOG(2) << "Rejoining a split control frame, opcode " << opcode; + AddToIncompleteControlFrameBody(data_buffer); + const int body_size = incomplete_control_frame_body_->offset(); + DCHECK_EQ(body_size, + static_cast<int>(current_frame_header_->payload_length)); + scoped_refptr<IOBufferWithSize> body = new IOBufferWithSize(body_size); + memcpy(body->data(), + incomplete_control_frame_body_->StartOfBuffer(), + body_size); + incomplete_control_frame_body_ = NULL; // Frame now complete. + DCHECK(is_final_chunk); + *frame = CreateFrame(is_final_chunk, body); + return OK; + } + } + + // Apply basic sanity checks to the |payload_length| field from the frame + // header. A check for exact equality can only be used when the whole frame + // arrives in one chunk. + DCHECK_GE(current_frame_header_->payload_length, + base::checked_numeric_cast<uint64>(chunk_size)); + DCHECK(!is_first_chunk || !is_final_chunk || + current_frame_header_->payload_length == + base::checked_numeric_cast<uint64>(chunk_size)); + + // Convert the chunk to a complete frame. + *frame = CreateFrame(is_final_chunk, data_buffer); + return OK; +} + +scoped_ptr<WebSocketFrame> WebSocketBasicStream::CreateFrame( + bool is_final_chunk, + const scoped_refptr<IOBufferWithSize>& data) { + scoped_ptr<WebSocketFrame> result_frame; + const bool is_final_chunk_in_message = + is_final_chunk && current_frame_header_->final; + const int data_size = data ? data->size() : 0; + const WebSocketFrameHeader::OpCode opcode = current_frame_header_->opcode; + // Empty frames convey no useful information unless they have the "final" bit + // set. + if (is_final_chunk_in_message || data_size > 0) { + result_frame.reset(new WebSocketFrame(opcode)); + result_frame->header.CopyFrom(*current_frame_header_); + result_frame->header.final = is_final_chunk_in_message; + result_frame->header.payload_length = data_size; + result_frame->data = data; + // Ensure that opcodes Text and Binary are only used for the first frame in + // the message. + if (WebSocketFrameHeader::IsKnownDataOpCode(opcode)) + current_frame_header_->opcode = WebSocketFrameHeader::kOpCodeContinuation; + } + // Make sure that a frame header is not applied to any chunks that do not + // belong to it. + if (is_final_chunk) + current_frame_header_.reset(); + return result_frame.Pass(); +} + +void WebSocketBasicStream::AddToIncompleteControlFrameBody( + const scoped_refptr<IOBufferWithSize>& data_buffer) { + if (!data_buffer) + return; + const int new_offset = + incomplete_control_frame_body_->offset() + data_buffer->size(); + CHECK_GE(incomplete_control_frame_body_->capacity(), new_offset) + << "Control frame body larger than frame header indicates; frame parser " + "bug?"; + memcpy(incomplete_control_frame_body_->data(), + data_buffer->data(), + data_buffer->size()); + incomplete_control_frame_body_->set_offset(new_offset); +} + +void WebSocketBasicStream::OnReadComplete(ScopedVector<WebSocketFrame>* frames, + const CompletionCallback& callback, + int result) { + result = HandleReadResult(result, frames); if (result == ERR_IO_PENDING) - result = ReadFrames(frame_chunks, callback); + result = ReadFrames(frames, callback); if (result != ERR_IO_PENDING) callback.Run(result); } diff --git a/net/websockets/websocket_basic_stream.h b/net/websockets/websocket_basic_stream.h index 1231da8..45c183c 100644 --- a/net/websockets/websocket_basic_stream.h +++ b/net/websockets/websocket_basic_stream.h @@ -22,6 +22,7 @@ class GrowableIOBuffer; class HttpRequestHeaders; class HttpResponseInfo; class IOBufferWithSize; +struct WebSocketFrame; struct WebSocketFrameChunk; // Implementation of WebSocketStream for non-multiplexed ws:// connections (or @@ -39,10 +40,10 @@ class NET_EXPORT_PRIVATE WebSocketBasicStream : public WebSocketStream { virtual ~WebSocketBasicStream(); // WebSocketStream implementation. - virtual int ReadFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks, + virtual int ReadFrames(ScopedVector<WebSocketFrame>* frames, const CompletionCallback& callback) OVERRIDE; - virtual int WriteFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks, + virtual int WriteFrames(ScopedVector<WebSocketFrame>* frames, const CompletionCallback& callback) OVERRIDE; virtual void Close() OVERRIDE; @@ -83,13 +84,43 @@ class NET_EXPORT_PRIVATE WebSocketBasicStream : public WebSocketStream { int result); // Attempts to parse the output of a read as WebSocket frames. On success, - // returns OK and places the frame(s) in frame_chunks. - int HandleReadResult(int result, - ScopedVector<WebSocketFrameChunk>* frame_chunks); + // returns OK and places the frame(s) in |frames|. + int HandleReadResult(int result, ScopedVector<WebSocketFrame>* frames); + + // Converts the chunks in |frame_chunks| into frames and writes them to + // |frames|. |frame_chunks| is destroyed in the process. Returns + // ERR_WS_PROTOCOL_ERROR if an invalid chunk was found. If one or more frames + // was added to |frames|, then returns OK, otherwise returns ERR_IO_PENDING. + int ConvertChunksToFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks, + ScopedVector<WebSocketFrame>* frames); + + // Converts a |chunk| to a |frame|. |*frame| should be NULL on entry to this + // method. If |chunk| is an incomplete control frame, or an empty non-final + // frame, then |*frame| may still be NULL on exit. If an invalid control frame + // is found, returns ERR_WS_PROTOCOL_ERROR and the stream is no longer + // usable. Otherwise returns OK (even if frame is still NULL). + int ConvertChunkToFrame(scoped_ptr<WebSocketFrameChunk> chunk, + scoped_ptr<WebSocketFrame>* frame); + + // Creates a frame based on the value of |is_final_chunk|, |data| and + // |current_frame_header_|. Clears |current_frame_header_| if |is_final_chunk| + // is true. |data| may be NULL if the frame has an empty payload. A frame with + // no data and the "final" flag not set is not useful; in this case the + // returned frame will be NULL. Otherwise, |current_frame_header_->opcode| is + // set to Continuation after use if it was Text or Binary, in accordance with + // WebSocket RFC6455 section 5.4. + scoped_ptr<WebSocketFrame> CreateFrame( + bool is_final_chunk, + const scoped_refptr<IOBufferWithSize>& data); + + // Adds |data_buffer| to the end of |incomplete_control_frame_body_|, applying + // bounds checks. + void AddToIncompleteControlFrameBody( + const scoped_refptr<IOBufferWithSize>& data_buffer); // Called when a read completes. Parses the result and (unless no complete // header has been received) calls |callback|. - void OnReadComplete(ScopedVector<WebSocketFrameChunk>* frame_chunks, + void OnReadComplete(ScopedVector<WebSocketFrame>* frames, const CompletionCallback& callback, int result); @@ -102,6 +133,18 @@ class NET_EXPORT_PRIVATE WebSocketBasicStream : public WebSocketStream { // from being returned to the pool. scoped_ptr<ClientSocketHandle> connection_; + // Frame header for the frame currently being received. Only non-NULL while we + // are processing the frame. If the frame arrives in multiple chunks, it can + // remain non-NULL until additional chunks arrive. If the header of the frame + // was invalid, this is set to NULL, the channel is failed, and subsequent + // chunks of the same frame will be ignored. + scoped_ptr<WebSocketFrameHeader> current_frame_header_; + + // Although it should rarely happen in practice, a control frame can arrive + // broken into chunks. This variable provides storage for a partial control + // frame until the rest arrives. It will be NULL the rest of the time. + scoped_refptr<GrowableIOBuffer> incomplete_control_frame_body_; + // Only used during handshake. Some data may be left in this buffer after the // handshake, in which case it will be picked up during the first call to // ReadFrames(). The type is GrowableIOBuffer for compatibility with diff --git a/net/websockets/websocket_basic_stream_test.cc b/net/websockets/websocket_basic_stream_test.cc index ec2e51a..37a49f6 100644 --- a/net/websockets/websocket_basic_stream_test.cc +++ b/net/websockets/websocket_basic_stream_test.cc @@ -37,33 +37,21 @@ const size_t kMultipleFramesSize = arraysize(kMultipleFrames) - 1; // invalid. const char kInvalidFrame[] = "\x81\x7E\x00\x07Invalid"; const size_t kInvalidFrameSize = arraysize(kInvalidFrame) - 1; +// Control frames must have the FIN bit set. This one does not. +const char kPingFrameWithoutFin[] = "\x09\x00"; +const size_t kPingFrameWithoutFinSize = arraysize(kPingFrameWithoutFin) - 1; +// Control frames must have a payload of 125 bytes or less. This one has +// a payload of 126 bytes. +const char k126BytePong[] = + "\x8a\x7e\x00\x7eZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"; +const size_t k126BytePongSize = arraysize(k126BytePong) - 1; +const char kCloseFrame[] = "\x88\x09\x03\xe8occludo"; +const size_t kCloseFrameSize = arraysize(kCloseFrame) - 1; const char kWriteFrame[] = "\x81\x85\x00\x00\x00\x00Write"; const size_t kWriteFrameSize = arraysize(kWriteFrame) - 1; const WebSocketMaskingKey kNulMaskingKey = {{'\0', '\0', '\0', '\0'}}; -// Generates a ScopedVector<WebSocketFrameChunk> which will have a wire format -// matching kWriteFrame. -ScopedVector<WebSocketFrameChunk> GenerateWriteFrame() { - scoped_ptr<WebSocketFrameChunk> chunk(new WebSocketFrameChunk); - const size_t payload_size = - kWriteFrameSize - (WebSocketFrameHeader::kBaseHeaderSize + - WebSocketFrameHeader::kMaskingKeyLength); - chunk->data = new IOBufferWithSize(payload_size); - memcpy(chunk->data->data(), - kWriteFrame + kWriteFrameSize - payload_size, - payload_size); - chunk->final_chunk = true; - scoped_ptr<WebSocketFrameHeader> header( - new WebSocketFrameHeader(WebSocketFrameHeader::kOpCodeText)); - header->final = true; - header->masked = true; - header->payload_length = payload_size; - chunk->header = header.Pass(); - ScopedVector<WebSocketFrameChunk> chunks; - chunks.push_back(chunk.release()); - return chunks.Pass(); -} - // A masking key generator function which generates the identity mask, // ie. "\0\0\0\0". WebSocketMaskingKey GenerateNulMaskingKey() { return kNulMaskingKey; } @@ -131,11 +119,6 @@ class WebSocketBasicStreamSocketTest : public WebSocketBasicStreamTest { CreateStream(reads, N, NULL, 0); } - template <size_t N> - void CreateWriteOnly(MockWrite (&writes)[N]) { - CreateStream(NULL, 0, writes, N); - } - void CreateNullStream() { CreateStream(NULL, 0, NULL, 0); } scoped_ptr<SocketDataProvider> socket_data_; @@ -143,66 +126,145 @@ class WebSocketBasicStreamSocketTest : public WebSocketBasicStreamTest { ClientSocketPoolHistograms histograms_; MockTransportClientSocketPool pool_; CapturingBoundNetLog(bound_net_log_); - ScopedVector<WebSocketFrameChunk> frame_chunks_; + ScopedVector<WebSocketFrame> frames_; TestCompletionCallback cb_; scoped_refptr<GrowableIOBuffer> http_read_buffer_; std::string sub_protocol_; std::string extensions_; }; +// A test fixture for the common case of tests that only perform a single read. +class WebSocketBasicStreamSocketSingleReadTest + : public WebSocketBasicStreamSocketTest { + protected: + void CreateRead(const MockRead& read) { + reads_[0] = read; + CreateStream(reads_, 1U, NULL, 0); + } + + MockRead reads_[1]; +}; + +// A test fixture for tests that perform chunked reads. +class WebSocketBasicStreamSocketChunkedReadTest + : public WebSocketBasicStreamSocketTest { + protected: + // Specify the behaviour if there aren't enough chunks to use all the data. If + // LAST_FRAME_BIG is specified, then the rest of the data will be + // put in the last chunk. If LAST_FRAME_NOT_BIG is specified, then the last + // frame will be no bigger than the rest of the frames (but it can be smaller, + // if not enough data remains). + enum LastFrameBehaviour { + LAST_FRAME_BIG, + LAST_FRAME_NOT_BIG + }; + + // Prepares a read from |data| of |data_size|, split into |number_of_chunks|, + // each of |chunk_size| (except that the last chunk may be larger or + // smaller). All reads must be either SYNCHRONOUS or ASYNC (not a mixture), + // and errors cannot be simulated. Once data is exhausted, further reads will + // return 0 (ie. connection closed). + void CreateChunkedRead(IoMode mode, + const char data[], + size_t data_size, + int chunk_size, + int number_of_chunks, + LastFrameBehaviour last_frame_behaviour) { + reads_.reset(new MockRead[number_of_chunks]); + const char* start = data; + for (int i = 0; i < number_of_chunks; ++i) { + int len = chunk_size; + const bool is_last_chunk = (i == number_of_chunks - 1); + if ((last_frame_behaviour == LAST_FRAME_BIG && is_last_chunk) || + static_cast<int>(data + data_size - start) < len) { + len = static_cast<int>(data + data_size - start); + } + reads_[i] = MockRead(mode, start, len); + start += len; + } + CreateStream(reads_.get(), number_of_chunks, NULL, 0); + } + + scoped_ptr<MockRead[]> reads_; +}; + +// Test fixture for write tests. +class WebSocketBasicStreamSocketWriteTest + : public WebSocketBasicStreamSocketTest { + protected: + // All write tests use the same frame, so it is easiest to create it during + // test creation. + virtual void SetUp() OVERRIDE { PrepareWriteFrame(); } + + // Creates a WebSocketFrame with a wire format matching kWriteFrame and adds + // it to |frames_|. + void PrepareWriteFrame() { + scoped_ptr<WebSocketFrame> frame( + new WebSocketFrame(WebSocketFrameHeader::kOpCodeText)); + const size_t payload_size = + kWriteFrameSize - (WebSocketFrameHeader::kBaseHeaderSize + + WebSocketFrameHeader::kMaskingKeyLength); + frame->data = new IOBuffer(payload_size); + memcpy(frame->data->data(), + kWriteFrame + kWriteFrameSize - payload_size, + payload_size); + WebSocketFrameHeader& header = frame->header; + header.final = true; + header.masked = true; + header.payload_length = payload_size; + frames_.push_back(frame.release()); + } + + // Creates a stream that expects the listed writes. + template <size_t N> + void CreateWriteOnly(MockWrite (&writes)[N]) { + CreateStream(NULL, 0, writes, N); + } +}; + TEST_F(WebSocketBasicStreamSocketTest, ConstructionWorks) { CreateNullStream(); } -TEST_F(WebSocketBasicStreamSocketTest, SyncReadWorks) { - MockRead reads[] = {MockRead(SYNCHRONOUS, kSampleFrame, kSampleFrameSize)}; - CreateReadOnly(reads); - int result = stream_->ReadFrames(&frame_chunks_, cb_.callback()); +TEST_F(WebSocketBasicStreamSocketSingleReadTest, SyncReadWorks) { + CreateRead(MockRead(SYNCHRONOUS, kSampleFrame, kSampleFrameSize)); + int result = stream_->ReadFrames(&frames_, cb_.callback()); EXPECT_EQ(OK, result); - ASSERT_EQ(1U, frame_chunks_.size()); - ASSERT_TRUE(frame_chunks_[0]->header); - EXPECT_EQ(GG_UINT64_C(6), frame_chunks_[0]->header->payload_length); - EXPECT_TRUE(frame_chunks_[0]->header->final); - EXPECT_TRUE(frame_chunks_[0]->final_chunk); + ASSERT_EQ(1U, frames_.size()); + EXPECT_EQ(GG_UINT64_C(6), frames_[0]->header.payload_length); + EXPECT_TRUE(frames_[0]->header.final); } -TEST_F(WebSocketBasicStreamSocketTest, AsyncReadWorks) { - MockRead reads[] = {MockRead(ASYNC, kSampleFrame, kSampleFrameSize)}; - CreateReadOnly(reads); - int result = stream_->ReadFrames(&frame_chunks_, cb_.callback()); +TEST_F(WebSocketBasicStreamSocketSingleReadTest, AsyncReadWorks) { + CreateRead(MockRead(ASYNC, kSampleFrame, kSampleFrameSize)); + int result = stream_->ReadFrames(&frames_, cb_.callback()); ASSERT_EQ(ERR_IO_PENDING, result); EXPECT_EQ(OK, cb_.WaitForResult()); - ASSERT_EQ(1U, frame_chunks_.size()); - ASSERT_TRUE(frame_chunks_[0]->header); - EXPECT_EQ(GG_UINT64_C(6), frame_chunks_[0]->header->payload_length); + ASSERT_EQ(1U, frames_.size()); + EXPECT_EQ(GG_UINT64_C(6), frames_[0]->header.payload_length); // Don't repeat all the tests from SyncReadWorks; just enough to be sure the // frame was really read. } // ReadFrames will not return a frame whose header has not been wholly received. -TEST_F(WebSocketBasicStreamSocketTest, HeaderFragmentedSync) { - MockRead reads[] = { - MockRead(SYNCHRONOUS, kSampleFrame, 1), - MockRead(SYNCHRONOUS, kSampleFrame + 1, kSampleFrameSize - 1)}; - CreateReadOnly(reads); - int result = stream_->ReadFrames(&frame_chunks_, cb_.callback()); +TEST_F(WebSocketBasicStreamSocketChunkedReadTest, HeaderFragmentedSync) { + CreateChunkedRead( + SYNCHRONOUS, kSampleFrame, kSampleFrameSize, 1, 2, LAST_FRAME_BIG); + int result = stream_->ReadFrames(&frames_, cb_.callback()); ASSERT_EQ(OK, result); - ASSERT_EQ(1U, frame_chunks_.size()); - ASSERT_TRUE(frame_chunks_[0]->header); - EXPECT_EQ(GG_UINT64_C(6), frame_chunks_[0]->header->payload_length); + ASSERT_EQ(1U, frames_.size()); + EXPECT_EQ(GG_UINT64_C(6), frames_[0]->header.payload_length); } // The same behaviour applies to asynchronous reads. -TEST_F(WebSocketBasicStreamSocketTest, HeaderFragmentedAsync) { - MockRead reads[] = {MockRead(ASYNC, kSampleFrame, 1), - MockRead(ASYNC, kSampleFrame + 1, kSampleFrameSize - 1)}; - CreateReadOnly(reads); - int result = stream_->ReadFrames(&frame_chunks_, cb_.callback()); +TEST_F(WebSocketBasicStreamSocketChunkedReadTest, HeaderFragmentedAsync) { + CreateChunkedRead( + ASYNC, kSampleFrame, kSampleFrameSize, 1, 2, LAST_FRAME_BIG); + int result = stream_->ReadFrames(&frames_, cb_.callback()); ASSERT_EQ(ERR_IO_PENDING, result); EXPECT_EQ(OK, cb_.WaitForResult()); - ASSERT_EQ(1U, frame_chunks_.size()); - ASSERT_TRUE(frame_chunks_[0]->header); - EXPECT_EQ(GG_UINT64_C(6), frame_chunks_[0]->header->payload_length); + ASSERT_EQ(1U, frames_.size()); + EXPECT_EQ(GG_UINT64_C(6), frames_[0]->header.payload_length); } // If it receives an incomplete header in a synchronous call, then has to wait @@ -211,12 +273,11 @@ TEST_F(WebSocketBasicStreamSocketTest, HeaderFragmentedSyncAsync) { MockRead reads[] = {MockRead(SYNCHRONOUS, kSampleFrame, 1), MockRead(ASYNC, kSampleFrame + 1, kSampleFrameSize - 1)}; CreateReadOnly(reads); - int result = stream_->ReadFrames(&frame_chunks_, cb_.callback()); + int result = stream_->ReadFrames(&frames_, cb_.callback()); ASSERT_EQ(ERR_IO_PENDING, result); EXPECT_EQ(OK, cb_.WaitForResult()); - ASSERT_EQ(1U, frame_chunks_.size()); - ASSERT_TRUE(frame_chunks_[0]->header); - EXPECT_EQ(GG_UINT64_C(6), frame_chunks_[0]->header->payload_length); + ASSERT_EQ(1U, frames_.size()); + EXPECT_EQ(GG_UINT64_C(6), frames_[0]->header.payload_length); } // An extended header should also return ERR_IO_PENDING if it is not completely @@ -226,112 +287,164 @@ TEST_F(WebSocketBasicStreamSocketTest, FragmentedLargeHeader) { MockRead(SYNCHRONOUS, kPartialLargeFrame, kLargeFrameHeaderSize - 1), MockRead(SYNCHRONOUS, ERR_IO_PENDING)}; CreateReadOnly(reads); - EXPECT_EQ(ERR_IO_PENDING, - stream_->ReadFrames(&frame_chunks_, cb_.callback())); + EXPECT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frames_, cb_.callback())); } -// A frame that does not arrive in a single read should arrive in chunks. -TEST_F(WebSocketBasicStreamSocketTest, LargeFrameFirstChunk) { - MockRead reads[] = { - MockRead(SYNCHRONOUS, kPartialLargeFrame, kPartialLargeFrameSize)}; - CreateReadOnly(reads); - EXPECT_EQ(OK, stream_->ReadFrames(&frame_chunks_, cb_.callback())); - ASSERT_EQ(1U, frame_chunks_.size()); - ASSERT_TRUE(frame_chunks_[0]->header); - EXPECT_EQ(kLargeFrameDeclaredPayloadSize, - frame_chunks_[0]->header->payload_length); - EXPECT_TRUE(frame_chunks_[0]->header->final); - EXPECT_FALSE(frame_chunks_[0]->final_chunk); +// A frame that does not arrive in a single read should be broken into separate +// frames. +TEST_F(WebSocketBasicStreamSocketSingleReadTest, LargeFrameFirstChunk) { + CreateRead(MockRead(SYNCHRONOUS, kPartialLargeFrame, kPartialLargeFrameSize)); + EXPECT_EQ(OK, stream_->ReadFrames(&frames_, cb_.callback())); + ASSERT_EQ(1U, frames_.size()); + EXPECT_FALSE(frames_[0]->header.final); EXPECT_EQ(kPartialLargeFrameSize - kLargeFrameHeaderSize, - static_cast<size_t>(frame_chunks_[0]->data->size())); + static_cast<size_t>(frames_[0]->header.payload_length)); } -// If only the header arrives, we should get a zero-byte chunk. +// If only the header of a data frame arrives, we should not receive a frame and +// be told to wait. WebSocketBasicStream does two reads in this case, as after +// the first read it has no frames to return. TEST_F(WebSocketBasicStreamSocketTest, HeaderOnlyChunk) { MockRead reads[] = { - MockRead(SYNCHRONOUS, kPartialLargeFrame, kLargeFrameHeaderSize)}; + MockRead(SYNCHRONOUS, kPartialLargeFrame, kLargeFrameHeaderSize), + MockRead(SYNCHRONOUS, ERR_IO_PENDING)}; CreateReadOnly(reads); - EXPECT_EQ(OK, stream_->ReadFrames(&frame_chunks_, cb_.callback())); - ASSERT_EQ(1U, frame_chunks_.size()); - EXPECT_FALSE(frame_chunks_[0]->final_chunk); - EXPECT_TRUE(frame_chunks_[0]->data.get() == NULL); + EXPECT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frames_, cb_.callback())); + ASSERT_EQ(0U, frames_.size()); } -// The second and subsequent chunks of a frame have no header. -TEST_F(WebSocketBasicStreamSocketTest, LargeFrameTwoChunks) { - static const size_t kChunkSize = 16; +// If the header and the body of a data frame arrive seperately, we should only +// see one frame. +TEST_F(WebSocketBasicStreamSocketTest, HeaderBodySeparated) { MockRead reads[] = { - MockRead(ASYNC, kPartialLargeFrame, kChunkSize), - MockRead(ASYNC, kPartialLargeFrame + kChunkSize, kChunkSize)}; + MockRead(SYNCHRONOUS, kPartialLargeFrame, kLargeFrameHeaderSize), + MockRead(ASYNC, + kPartialLargeFrame + kLargeFrameHeaderSize, + kPartialLargeFrameSize - kLargeFrameHeaderSize)}; CreateReadOnly(reads); + EXPECT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frames_, cb_.callback())); + EXPECT_EQ(OK, cb_.WaitForResult()); + ASSERT_EQ(1U, frames_.size()); + EXPECT_EQ(kPartialLargeFrameSize - kLargeFrameHeaderSize, + frames_[0]->header.payload_length); +} + +// If the header and body of a data frame arrive separately, the frame we see +// should have the opcode from the header (not Continuation). +TEST_F(WebSocketBasicStreamSocketTest, HeaderBodySeparatedOpCodeNotLost) { + MockRead reads[] = { + MockRead(ASYNC, kPartialLargeFrame, kLargeFrameHeaderSize), + MockRead(ASYNC, + kPartialLargeFrame + kLargeFrameHeaderSize, + kPartialLargeFrameSize - kLargeFrameHeaderSize)}; + CreateReadOnly(reads); + EXPECT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frames_, cb_.callback())); + EXPECT_EQ(OK, cb_.WaitForResult()); + ASSERT_EQ(1U, frames_.size()); + EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, frames_[0]->header.opcode); +} + +// Every frame has a header with a correct payload_length field. +TEST_F(WebSocketBasicStreamSocketChunkedReadTest, LargeFrameTwoChunks) { + const size_t kChunkSize = 16; + CreateChunkedRead(ASYNC, + kPartialLargeFrame, + kPartialLargeFrameSize, + kChunkSize, + 2, + LAST_FRAME_NOT_BIG); TestCompletionCallback cb[2]; - ASSERT_EQ(ERR_IO_PENDING, - stream_->ReadFrames(&frame_chunks_, cb[0].callback())); + ASSERT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frames_, cb[0].callback())); EXPECT_EQ(OK, cb[0].WaitForResult()); - ASSERT_EQ(1U, frame_chunks_.size()); - ASSERT_TRUE(frame_chunks_[0]->header); + ASSERT_EQ(1U, frames_.size()); + EXPECT_EQ(kChunkSize - kLargeFrameHeaderSize, + frames_[0]->header.payload_length); - frame_chunks_.clear(); - ASSERT_EQ(ERR_IO_PENDING, - stream_->ReadFrames(&frame_chunks_, cb[1].callback())); + frames_.clear(); + ASSERT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frames_, cb[1].callback())); EXPECT_EQ(OK, cb[1].WaitForResult()); - ASSERT_EQ(1U, frame_chunks_.size()); - ASSERT_FALSE(frame_chunks_[0]->header); + ASSERT_EQ(1U, frames_.size()); + EXPECT_EQ(kChunkSize, frames_[0]->header.payload_length); } -// Only the final chunk of a frame has final_chunk set. -TEST_F(WebSocketBasicStreamSocketTest, OnlyFinalChunkIsFinal) { +// Only the final frame of a fragmented message has |final| bit set. +TEST_F(WebSocketBasicStreamSocketChunkedReadTest, OnlyFinalChunkIsFinal) { static const size_t kFirstChunkSize = 4; - MockRead reads[] = {MockRead(ASYNC, kSampleFrame, kFirstChunkSize), - MockRead(ASYNC, - kSampleFrame + kFirstChunkSize, - kSampleFrameSize - kFirstChunkSize)}; - CreateReadOnly(reads); + CreateChunkedRead(ASYNC, + kSampleFrame, + kSampleFrameSize, + kFirstChunkSize, + 2, + LAST_FRAME_BIG); TestCompletionCallback cb[2]; - ASSERT_EQ(ERR_IO_PENDING, - stream_->ReadFrames(&frame_chunks_, cb[0].callback())); + ASSERT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frames_, cb[0].callback())); EXPECT_EQ(OK, cb[0].WaitForResult()); - ASSERT_EQ(1U, frame_chunks_.size()); - ASSERT_FALSE(frame_chunks_[0]->final_chunk); + ASSERT_EQ(1U, frames_.size()); + ASSERT_FALSE(frames_[0]->header.final); - frame_chunks_.clear(); - ASSERT_EQ(ERR_IO_PENDING, - stream_->ReadFrames(&frame_chunks_, cb[1].callback())); + frames_.clear(); + ASSERT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frames_, cb[1].callback())); EXPECT_EQ(OK, cb[1].WaitForResult()); - ASSERT_EQ(1U, frame_chunks_.size()); - ASSERT_TRUE(frame_chunks_[0]->final_chunk); + ASSERT_EQ(1U, frames_.size()); + ASSERT_TRUE(frames_[0]->header.final); } -// Multiple frames that arrive together should be parsed correctly. -TEST_F(WebSocketBasicStreamSocketTest, ThreeFramesTogether) { - MockRead reads[] = { - MockRead(SYNCHRONOUS, kMultipleFrames, kMultipleFramesSize)}; - CreateReadOnly(reads); +// All frames after the first have their opcode changed to Continuation. +TEST_F(WebSocketBasicStreamSocketChunkedReadTest, ContinuationOpCodeUsed) { + const size_t kFirstChunkSize = 3; + const int kChunkCount = 3; + // The input data is one frame with opcode Text, which arrives in three + // separate chunks. + CreateChunkedRead(ASYNC, + kSampleFrame, + kSampleFrameSize, + kFirstChunkSize, + kChunkCount, + LAST_FRAME_BIG); + TestCompletionCallback cb[kChunkCount]; + + ASSERT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frames_, cb[0].callback())); + EXPECT_EQ(OK, cb[0].WaitForResult()); + ASSERT_EQ(1U, frames_.size()); + EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, frames_[0]->header.opcode); + + // This test uses a loop to verify that the opcode for every frames generated + // after the first is converted to Continuation. + for (int i = 1; i < kChunkCount; ++i) { + frames_.clear(); + ASSERT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frames_, cb[i].callback())); + EXPECT_EQ(OK, cb[i].WaitForResult()); + ASSERT_EQ(1U, frames_.size()); + EXPECT_EQ(WebSocketFrameHeader::kOpCodeContinuation, + frames_[0]->header.opcode); + } +} - ASSERT_EQ(OK, stream_->ReadFrames(&frame_chunks_, cb_.callback())); - ASSERT_EQ(3U, frame_chunks_.size()); - EXPECT_TRUE(frame_chunks_[0]->final_chunk); - EXPECT_TRUE(frame_chunks_[1]->final_chunk); - EXPECT_TRUE(frame_chunks_[2]->final_chunk); +// Multiple frames that arrive together should be parsed correctly. +TEST_F(WebSocketBasicStreamSocketSingleReadTest, ThreeFramesTogether) { + CreateRead(MockRead(SYNCHRONOUS, kMultipleFrames, kMultipleFramesSize)); + + ASSERT_EQ(OK, stream_->ReadFrames(&frames_, cb_.callback())); + ASSERT_EQ(3U, frames_.size()); + EXPECT_TRUE(frames_[0]->header.final); + EXPECT_TRUE(frames_[1]->header.final); + EXPECT_TRUE(frames_[2]->header.final); } // ERR_CONNECTION_CLOSED must be returned on close. -TEST_F(WebSocketBasicStreamSocketTest, SyncClose) { - MockRead reads[] = {MockRead(SYNCHRONOUS, "", 0)}; - CreateReadOnly(reads); +TEST_F(WebSocketBasicStreamSocketSingleReadTest, SyncClose) { + CreateRead(MockRead(SYNCHRONOUS, "", 0)); EXPECT_EQ(ERR_CONNECTION_CLOSED, - stream_->ReadFrames(&frame_chunks_, cb_.callback())); + stream_->ReadFrames(&frames_, cb_.callback())); } -TEST_F(WebSocketBasicStreamSocketTest, AsyncClose) { - MockRead reads[] = {MockRead(ASYNC, "", 0)}; - CreateReadOnly(reads); +TEST_F(WebSocketBasicStreamSocketSingleReadTest, AsyncClose) { + CreateRead(MockRead(ASYNC, "", 0)); - ASSERT_EQ(ERR_IO_PENDING, - stream_->ReadFrames(&frame_chunks_, cb_.callback())); + ASSERT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frames_, cb_.callback())); EXPECT_EQ(ERR_CONNECTION_CLOSED, cb_.WaitForResult()); } @@ -339,53 +452,52 @@ TEST_F(WebSocketBasicStreamSocketTest, AsyncClose) { // ERR_CONNECTION_CLOSED. This is not expected to happen on an established // connection; a Read of size 0 is the expected behaviour. The key point of this // test is to confirm that ReadFrames() behaviour is identical in both cases. -TEST_F(WebSocketBasicStreamSocketTest, SyncCloseWithErr) { - MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_CONNECTION_CLOSED)}; - CreateReadOnly(reads); +TEST_F(WebSocketBasicStreamSocketSingleReadTest, SyncCloseWithErr) { + CreateRead(MockRead(SYNCHRONOUS, ERR_CONNECTION_CLOSED)); EXPECT_EQ(ERR_CONNECTION_CLOSED, - stream_->ReadFrames(&frame_chunks_, cb_.callback())); + stream_->ReadFrames(&frames_, cb_.callback())); } -TEST_F(WebSocketBasicStreamSocketTest, AsyncCloseWithErr) { - MockRead reads[] = {MockRead(ASYNC, ERR_CONNECTION_CLOSED)}; - CreateReadOnly(reads); +TEST_F(WebSocketBasicStreamSocketSingleReadTest, AsyncCloseWithErr) { + CreateRead(MockRead(ASYNC, ERR_CONNECTION_CLOSED)); - ASSERT_EQ(ERR_IO_PENDING, - stream_->ReadFrames(&frame_chunks_, cb_.callback())); + ASSERT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frames_, cb_.callback())); EXPECT_EQ(ERR_CONNECTION_CLOSED, cb_.WaitForResult()); } -TEST_F(WebSocketBasicStreamSocketTest, SyncErrorsPassedThrough) { +TEST_F(WebSocketBasicStreamSocketSingleReadTest, SyncErrorsPassedThrough) { // ERR_INSUFFICIENT_RESOURCES here represents an arbitrary error that // WebSocketBasicStream gives no special handling to. - MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_INSUFFICIENT_RESOURCES)}; - CreateReadOnly(reads); + CreateRead(MockRead(SYNCHRONOUS, ERR_INSUFFICIENT_RESOURCES)); EXPECT_EQ(ERR_INSUFFICIENT_RESOURCES, - stream_->ReadFrames(&frame_chunks_, cb_.callback())); + stream_->ReadFrames(&frames_, cb_.callback())); } -TEST_F(WebSocketBasicStreamSocketTest, AsyncErrorsPassedThrough) { - MockRead reads[] = {MockRead(ASYNC, ERR_INSUFFICIENT_RESOURCES)}; - CreateReadOnly(reads); +TEST_F(WebSocketBasicStreamSocketSingleReadTest, AsyncErrorsPassedThrough) { + CreateRead(MockRead(ASYNC, ERR_INSUFFICIENT_RESOURCES)); - ASSERT_EQ(ERR_IO_PENDING, - stream_->ReadFrames(&frame_chunks_, cb_.callback())); + ASSERT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frames_, cb_.callback())); EXPECT_EQ(ERR_INSUFFICIENT_RESOURCES, cb_.WaitForResult()); } // If we get a frame followed by a close, we should receive them separately. -TEST_F(WebSocketBasicStreamSocketTest, CloseAfterFrame) { - MockRead reads[] = {MockRead(SYNCHRONOUS, kSampleFrame, kSampleFrameSize), - MockRead(SYNCHRONOUS, "", 0)}; - CreateReadOnly(reads); - - EXPECT_EQ(OK, stream_->ReadFrames(&frame_chunks_, cb_.callback())); - EXPECT_EQ(1U, frame_chunks_.size()); - frame_chunks_.clear(); +TEST_F(WebSocketBasicStreamSocketChunkedReadTest, CloseAfterFrame) { + // The chunk size equals the data size, so the second chunk is 0 size, closing + // the connection. + CreateChunkedRead(SYNCHRONOUS, + kSampleFrame, + kSampleFrameSize, + kSampleFrameSize, + 2, + LAST_FRAME_NOT_BIG); + + EXPECT_EQ(OK, stream_->ReadFrames(&frames_, cb_.callback())); + EXPECT_EQ(1U, frames_.size()); + frames_.clear(); EXPECT_EQ(ERR_CONNECTION_CLOSED, - stream_->ReadFrames(&frame_chunks_, cb_.callback())); + stream_->ReadFrames(&frames_, cb_.callback())); } // Synchronous close after an async frame header is handled by a different code @@ -395,8 +507,7 @@ TEST_F(WebSocketBasicStreamSocketTest, AsyncCloseAfterIncompleteHeader) { MockRead(SYNCHRONOUS, "", 0)}; CreateReadOnly(reads); - ASSERT_EQ(ERR_IO_PENDING, - stream_->ReadFrames(&frame_chunks_, cb_.callback())); + ASSERT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frames_, cb_.callback())); ASSERT_EQ(ERR_CONNECTION_CLOSED, cb_.WaitForResult()); } @@ -407,8 +518,7 @@ TEST_F(WebSocketBasicStreamSocketTest, AsyncErrCloseAfterIncompleteHeader) { MockRead(SYNCHRONOUS, ERR_CONNECTION_CLOSED)}; CreateReadOnly(reads); - ASSERT_EQ(ERR_IO_PENDING, - stream_->ReadFrames(&frame_chunks_, cb_.callback())); + ASSERT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frames_, cb_.callback())); ASSERT_EQ(ERR_CONNECTION_CLOSED, cb_.WaitForResult()); } @@ -418,80 +528,186 @@ TEST_F(WebSocketBasicStreamSocketTest, HttpReadBufferIsUsed) { SetHttpReadBuffer(kSampleFrame, kSampleFrameSize); CreateNullStream(); - EXPECT_EQ(OK, stream_->ReadFrames(&frame_chunks_, cb_.callback())); - ASSERT_EQ(1U, frame_chunks_.size()); - ASSERT_TRUE(frame_chunks_[0]->data); - EXPECT_EQ(6, frame_chunks_[0]->data->size()); + EXPECT_EQ(OK, stream_->ReadFrames(&frames_, cb_.callback())); + ASSERT_EQ(1U, frames_.size()); + ASSERT_TRUE(frames_[0]->data); + EXPECT_EQ(GG_UINT64_C(6), frames_[0]->header.payload_length); } // Check that a frame whose header partially arrived at the end of the response // headers works correctly. -TEST_F(WebSocketBasicStreamSocketTest, PartialFrameHeaderInHttpResponse) { +TEST_F(WebSocketBasicStreamSocketSingleReadTest, + PartialFrameHeaderInHttpResponse) { SetHttpReadBuffer(kSampleFrame, 1); - MockRead reads[] = {MockRead(ASYNC, kSampleFrame + 1, kSampleFrameSize - 1)}; - CreateReadOnly(reads); + CreateRead(MockRead(ASYNC, kSampleFrame + 1, kSampleFrameSize - 1)); - ASSERT_EQ(ERR_IO_PENDING, - stream_->ReadFrames(&frame_chunks_, cb_.callback())); + ASSERT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frames_, cb_.callback())); EXPECT_EQ(OK, cb_.WaitForResult()); - ASSERT_EQ(1U, frame_chunks_.size()); - ASSERT_TRUE(frame_chunks_[0]->data); - EXPECT_EQ(6, frame_chunks_[0]->data->size()); - ASSERT_TRUE(frame_chunks_[0]->header); - EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, - frame_chunks_[0]->header->opcode); + ASSERT_EQ(1U, frames_.size()); + ASSERT_TRUE(frames_[0]->data); + EXPECT_EQ(GG_UINT64_C(6), frames_[0]->header.payload_length); + EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, frames_[0]->header.opcode); +} + +// Check that a control frame which partially arrives at the end of the response +// headers works correctly. +TEST_F(WebSocketBasicStreamSocketSingleReadTest, + PartialControlFrameInHttpResponse) { + const size_t kPartialFrameBytes = 3; + SetHttpReadBuffer(kCloseFrame, kPartialFrameBytes); + CreateRead(MockRead(ASYNC, + kCloseFrame + kPartialFrameBytes, + kCloseFrameSize - kPartialFrameBytes)); + + ASSERT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frames_, cb_.callback())); + EXPECT_EQ(OK, cb_.WaitForResult()); + ASSERT_EQ(1U, frames_.size()); + EXPECT_EQ(WebSocketFrameHeader::kOpCodeClose, frames_[0]->header.opcode); + EXPECT_EQ(kCloseFrameSize - 2, frames_[0]->header.payload_length); + EXPECT_EQ( + 0, + memcmp(frames_[0]->data->data(), kCloseFrame + 2, kCloseFrameSize - 2)); +} + +// Check that a control frame which partially arrives at the end of the response +// headers works correctly. Synchronous version (unlikely in practice). +TEST_F(WebSocketBasicStreamSocketSingleReadTest, + PartialControlFrameInHttpResponseSync) { + const size_t kPartialFrameBytes = 3; + SetHttpReadBuffer(kCloseFrame, kPartialFrameBytes); + CreateRead(MockRead(SYNCHRONOUS, + kCloseFrame + kPartialFrameBytes, + kCloseFrameSize - kPartialFrameBytes)); + + ASSERT_EQ(OK, stream_->ReadFrames(&frames_, cb_.callback())); + ASSERT_EQ(1U, frames_.size()); + EXPECT_EQ(WebSocketFrameHeader::kOpCodeClose, frames_[0]->header.opcode); } // Check that an invalid frame results in an error. -TEST_F(WebSocketBasicStreamSocketTest, SyncInvalidFrame) { - MockRead reads[] = {MockRead(SYNCHRONOUS, kInvalidFrame, kInvalidFrameSize)}; - CreateReadOnly(reads); +TEST_F(WebSocketBasicStreamSocketSingleReadTest, SyncInvalidFrame) { + CreateRead(MockRead(SYNCHRONOUS, kInvalidFrame, kInvalidFrameSize)); EXPECT_EQ(ERR_WS_PROTOCOL_ERROR, - stream_->ReadFrames(&frame_chunks_, cb_.callback())); + stream_->ReadFrames(&frames_, cb_.callback())); } -TEST_F(WebSocketBasicStreamSocketTest, AsyncInvalidFrame) { - MockRead reads[] = {MockRead(ASYNC, kInvalidFrame, kInvalidFrameSize)}; - CreateReadOnly(reads); +TEST_F(WebSocketBasicStreamSocketSingleReadTest, AsyncInvalidFrame) { + CreateRead(MockRead(ASYNC, kInvalidFrame, kInvalidFrameSize)); + + ASSERT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frames_, cb_.callback())); + EXPECT_EQ(ERR_WS_PROTOCOL_ERROR, cb_.WaitForResult()); +} + +// A control frame without a FIN flag is invalid and should not be passed +// through to higher layers. RFC6455 5.5 "All control frames ... MUST NOT be +// fragmented." +TEST_F(WebSocketBasicStreamSocketSingleReadTest, ControlFrameWithoutFin) { + CreateRead( + MockRead(SYNCHRONOUS, kPingFrameWithoutFin, kPingFrameWithoutFinSize)); + + ASSERT_EQ(ERR_WS_PROTOCOL_ERROR, + stream_->ReadFrames(&frames_, cb_.callback())); + EXPECT_TRUE(frames_.empty()); +} + +// A control frame over 125 characters is invalid. RFC6455 5.5 "All control +// frames MUST have a payload length of 125 bytes or less". Since we use a +// 125-byte buffer to assemble fragmented control frames, we need to detect this +// error before attempting to assemble the fragments. +TEST_F(WebSocketBasicStreamSocketSingleReadTest, OverlongControlFrame) { + CreateRead(MockRead(SYNCHRONOUS, k126BytePong, k126BytePongSize)); + + EXPECT_EQ(ERR_WS_PROTOCOL_ERROR, + stream_->ReadFrames(&frames_, cb_.callback())); + EXPECT_TRUE(frames_.empty()); +} + +// A control frame over 125 characters should still be rejected if it is split +// into multiple chunks. +TEST_F(WebSocketBasicStreamSocketChunkedReadTest, SplitOverlongControlFrame) { + const size_t kFirstChunkSize = 16; + CreateChunkedRead(SYNCHRONOUS, + k126BytePong, + k126BytePongSize, + kFirstChunkSize, + 2, + LAST_FRAME_BIG); + + EXPECT_EQ(ERR_WS_PROTOCOL_ERROR, + stream_->ReadFrames(&frames_, cb_.callback())); + EXPECT_TRUE(frames_.empty()); +} - ASSERT_EQ(ERR_IO_PENDING, - stream_->ReadFrames(&frame_chunks_, cb_.callback())); +TEST_F(WebSocketBasicStreamSocketChunkedReadTest, + AsyncSplitOverlongControlFrame) { + const size_t kFirstChunkSize = 16; + CreateChunkedRead(ASYNC, + k126BytePong, + k126BytePongSize, + kFirstChunkSize, + 2, + LAST_FRAME_BIG); + + ASSERT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frames_, cb_.callback())); EXPECT_EQ(ERR_WS_PROTOCOL_ERROR, cb_.WaitForResult()); + // The caller should not call ReadFrames() again after receiving an error + // other than ERR_IO_PENDING. + EXPECT_TRUE(frames_.empty()); +} + +// In the synchronous case, ReadFrames assembles the whole control frame before +// returning. +TEST_F(WebSocketBasicStreamSocketChunkedReadTest, SyncControlFrameAssembly) { + const size_t kChunkSize = 3; + CreateChunkedRead( + SYNCHRONOUS, kCloseFrame, kCloseFrameSize, kChunkSize, 3, LAST_FRAME_BIG); + + ASSERT_EQ(OK, stream_->ReadFrames(&frames_, cb_.callback())); + ASSERT_EQ(1U, frames_.size()); + EXPECT_EQ(WebSocketFrameHeader::kOpCodeClose, frames_[0]->header.opcode); +} + +// In the asynchronous case, the callback is not called until the control frame +// has been completely assembled. +TEST_F(WebSocketBasicStreamSocketChunkedReadTest, AsyncControlFrameAssembly) { + const size_t kChunkSize = 3; + CreateChunkedRead( + ASYNC, kCloseFrame, kCloseFrameSize, kChunkSize, 3, LAST_FRAME_BIG); + + ASSERT_EQ(ERR_IO_PENDING, stream_->ReadFrames(&frames_, cb_.callback())); + ASSERT_EQ(OK, cb_.WaitForResult()); + ASSERT_EQ(1U, frames_.size()); + EXPECT_EQ(WebSocketFrameHeader::kOpCodeClose, frames_[0]->header.opcode); } // Check that writing a frame all at once works. -TEST_F(WebSocketBasicStreamSocketTest, WriteAtOnce) { +TEST_F(WebSocketBasicStreamSocketWriteTest, WriteAtOnce) { MockWrite writes[] = {MockWrite(SYNCHRONOUS, kWriteFrame, kWriteFrameSize)}; CreateWriteOnly(writes); - frame_chunks_ = GenerateWriteFrame(); - EXPECT_EQ(OK, stream_->WriteFrames(&frame_chunks_, cb_.callback())); + EXPECT_EQ(OK, stream_->WriteFrames(&frames_, cb_.callback())); } // Check that completely async writing works. -TEST_F(WebSocketBasicStreamSocketTest, AsyncWriteAtOnce) { +TEST_F(WebSocketBasicStreamSocketWriteTest, AsyncWriteAtOnce) { MockWrite writes[] = {MockWrite(ASYNC, kWriteFrame, kWriteFrameSize)}; CreateWriteOnly(writes); - frame_chunks_ = GenerateWriteFrame(); - ASSERT_EQ(ERR_IO_PENDING, - stream_->WriteFrames(&frame_chunks_, cb_.callback())); + ASSERT_EQ(ERR_IO_PENDING, stream_->WriteFrames(&frames_, cb_.callback())); EXPECT_EQ(OK, cb_.WaitForResult()); } // Check that writing a frame to an extremely full kernel buffer (so that it // ends up being sent in bits) works. The WriteFrames() callback should not be // called until all parts have been written. -TEST_F(WebSocketBasicStreamSocketTest, WriteInBits) { +TEST_F(WebSocketBasicStreamSocketWriteTest, WriteInBits) { MockWrite writes[] = {MockWrite(SYNCHRONOUS, kWriteFrame, 4), MockWrite(ASYNC, kWriteFrame + 4, 4), MockWrite(ASYNC, kWriteFrame + 8, kWriteFrameSize - 8)}; CreateWriteOnly(writes); - frame_chunks_ = GenerateWriteFrame(); - ASSERT_EQ(ERR_IO_PENDING, - stream_->WriteFrames(&frame_chunks_, cb_.callback())); + ASSERT_EQ(ERR_IO_PENDING, stream_->WriteFrames(&frames_, cb_.callback())); EXPECT_EQ(OK, cb_.WaitForResult()); } diff --git a/net/websockets/websocket_channel.cc b/net/websockets/websocket_channel.cc index 3db457c..d2150d4 100644 --- a/net/websockets/websocket_channel.cc +++ b/net/websockets/websocket_channel.cc @@ -27,10 +27,6 @@ const int kDefaultSendQuotaLowWaterMark = 1 << 16; const int kDefaultSendQuotaHighWaterMark = 1 << 17; const size_t kWebSocketCloseCodeLength = 2; -// This uses type uint64 to match the definition of -// WebSocketFrameHeader::payload_length in websocket_frame.h. -const uint64 kMaxControlFramePayload = 125; - } // namespace // A class to encapsulate a set of frames and information about the size of @@ -39,26 +35,25 @@ class WebSocketChannel::SendBuffer { public: SendBuffer() : total_bytes_(0) {} - // Add a WebSocketFrameChunk to the buffer and increase total_bytes_. - void AddFrame(scoped_ptr<WebSocketFrameChunk> chunk); + // Add a WebSocketFrame to the buffer and increase total_bytes_. + void AddFrame(scoped_ptr<WebSocketFrame> chunk); // Return a pointer to the frames_ for write purposes. - ScopedVector<WebSocketFrameChunk>* frames() { return &frames_; } + ScopedVector<WebSocketFrame>* frames() { return &frames_; } private: // The frames_ that will be sent in the next call to WriteFrames(). - ScopedVector<WebSocketFrameChunk> frames_; + ScopedVector<WebSocketFrame> frames_; - // The total size of the buffers in |frames_|. This will be used to measure - // the throughput of the link. + // The total size of the payload data in |frames_|. This will be used to + // measure the throughput of the link. // TODO(ricea): Measure the throughput of the link. size_t total_bytes_; }; -void WebSocketChannel::SendBuffer::AddFrame( - scoped_ptr<WebSocketFrameChunk> chunk) { - total_bytes_ += chunk->data->size(); - frames_.push_back(chunk.release()); +void WebSocketChannel::SendBuffer::AddFrame(scoped_ptr<WebSocketFrame> frame) { + total_bytes_ += frame->header.payload_length; + frames_.push_back(frame.release()); } // Implementation of WebSocketStream::ConnectDelegate that simply forwards the @@ -98,7 +93,7 @@ WebSocketChannel::WebSocketChannel( state_(FRESHLY_CONSTRUCTED) {} WebSocketChannel::~WebSocketChannel() { - // The stream may hold a pointer to read_frame_chunks_, and so it needs to be + // The stream may hold a pointer to read_frames_, and so it needs to be // destroyed first. stream_.reset(); } @@ -163,9 +158,9 @@ void WebSocketChannel::SendFrame(bool fin, // water mark" and "high water mark", but only if the link to the WebSocket // server is not saturated. // TODO(ricea): For kOpCodeText, do UTF-8 validation? - scoped_refptr<IOBufferWithSize> buffer(new IOBufferWithSize(data.size())); + scoped_refptr<IOBuffer> buffer(new IOBuffer(data.size())); std::copy(data.begin(), data.end(), buffer->data()); - SendIOBufferWithSize(fin, op_code, buffer); + SendIOBuffer(fin, op_code, buffer, data.size()); } void WebSocketChannel::SendFlowControl(int64 quota) { @@ -308,7 +303,7 @@ void WebSocketChannel::ReadFrames() { // WebSocketStream, and any pending reads will be cancelled when it is // destroyed. result = stream_->ReadFrames( - &read_frame_chunks_, + &read_frames_, base::Bind( &WebSocketChannel::OnReadDone, base::Unretained(this), false)); if (result != ERR_IO_PENDING) { @@ -325,20 +320,27 @@ void WebSocketChannel::OnReadDone(bool synchronous, int result) { case OK: // ReadFrames() must use ERR_CONNECTION_CLOSED for a closed connection // with no data read, not an empty response. - DCHECK(!read_frame_chunks_.empty()) + DCHECK(!read_frames_.empty()) << "ReadFrames() returned OK, but nothing was read."; - for (size_t i = 0; i < read_frame_chunks_.size(); ++i) { - scoped_ptr<WebSocketFrameChunk> chunk(read_frame_chunks_[i]); - read_frame_chunks_[i] = NULL; - ProcessFrameChunk(chunk.Pass()); + for (size_t i = 0; i < read_frames_.size(); ++i) { + scoped_ptr<WebSocketFrame> frame(read_frames_[i]); + read_frames_[i] = NULL; + ProcessFrame(frame.Pass()); } - read_frame_chunks_.clear(); + read_frames_.clear(); // There should always be a call to ReadFrames pending. + // TODO(ricea): Unless we are out of quota. if (!synchronous && state_ != CLOSED) { ReadFrames(); } return; + case ERR_WS_PROTOCOL_ERROR: + FailChannel(SEND_REAL_ERROR, + kWebSocketErrorProtocolError, + "WebSocket Protocol Error"); + return; + default: DCHECK_LT(result, 0) << "ReadFrames() should only return OK or ERR_ codes"; @@ -357,119 +359,33 @@ void WebSocketChannel::OnReadDone(bool synchronous, int result) { } } -void WebSocketChannel::ProcessFrameChunk( - scoped_ptr<WebSocketFrameChunk> chunk) { - bool is_first_chunk = false; - if (chunk->header) { - DCHECK(current_frame_header_ == NULL) - << "Received the header for a new frame without notification that " - << "the previous frame was complete."; - is_first_chunk = true; - current_frame_header_.swap(chunk->header); - if (current_frame_header_->masked) { - // RFC6455 Section 5.1 "A client MUST close a connection if it detects a - // masked frame." - FailChannel(SEND_REAL_ERROR, - kWebSocketErrorProtocolError, - "Masked frame from server"); - return; - } - } - if (!current_frame_header_) { - // If this channel rejected the previous chunk as invalid, then it will have - // reset |current_frame_header_| and closed the channel. More chunks of the - // invalid frame may still arrive, and it is not necessarily a bug for that - // to happen. However, if this happens when state_ is CONNECTED, it is - // definitely a bug. - DCHECK(state_ != CONNECTED) << "Unexpected header-less frame received " - << "(final_chunk = " << chunk->final_chunk - << ", data size = " << chunk->data->size() - << ")"; +void WebSocketChannel::ProcessFrame(scoped_ptr<WebSocketFrame> frame) { + if (frame->header.masked) { + // RFC6455 Section 5.1 "A client MUST close a connection if it detects a + // masked frame." + FailChannel(SEND_REAL_ERROR, + kWebSocketErrorProtocolError, + "Masked frame from server"); return; } - scoped_refptr<IOBufferWithSize> data_buffer; - data_buffer.swap(chunk->data); - const bool is_final_chunk = chunk->final_chunk; - chunk.reset(); - const WebSocketFrameHeader::OpCode opcode = current_frame_header_->opcode; - if (WebSocketFrameHeader::IsKnownControlOpCode(opcode)) { - if (!current_frame_header_->final) { - FailChannel(SEND_REAL_ERROR, - kWebSocketErrorProtocolError, - "Control message with FIN bit unset received"); - return; - } - if (current_frame_header_->payload_length > kMaxControlFramePayload) { - FailChannel(SEND_REAL_ERROR, - kWebSocketErrorProtocolError, - "Control message has payload over 125 bytes"); - return; - } - if (!is_final_chunk) { - VLOG(2) << "Encountered a split control frame, opcode " << opcode; - if (incomplete_control_frame_body_) { - VLOG(3) << "Appending to an existing split control frame."; - AddToIncompleteControlFrameBody(data_buffer); - } else { - VLOG(3) << "Creating new storage for an incomplete control frame."; - incomplete_control_frame_body_ = new GrowableIOBuffer(); - // This method checks for oversize control frames above, so as long as - // the frame parser is working correctly, this won't overflow. If a bug - // does cause it to overflow, it will CHECK() in - // AddToIncompleteControlFrameBody() without writing outside the buffer. - incomplete_control_frame_body_->SetCapacity(kMaxControlFramePayload); - AddToIncompleteControlFrameBody(data_buffer); - } - return; // Handle when complete. - } - if (incomplete_control_frame_body_) { - VLOG(2) << "Rejoining a split control frame, opcode " << opcode; - AddToIncompleteControlFrameBody(data_buffer); - const int body_size = incomplete_control_frame_body_->offset(); - data_buffer = new IOBufferWithSize(body_size); - memcpy(data_buffer->data(), - incomplete_control_frame_body_->StartOfBuffer(), - body_size); - incomplete_control_frame_body_ = NULL; // Frame now complete. - } + const WebSocketFrameHeader::OpCode opcode = frame->header.opcode; + if (WebSocketFrameHeader::IsKnownControlOpCode(opcode) && + !frame->header.final) { + FailChannel(SEND_REAL_ERROR, + kWebSocketErrorProtocolError, + "Control message with FIN bit unset received"); + return; } - // Apply basic sanity checks to the |payload_length| field from the frame - // header. A check for exact equality can only be used when the whole frame - // arrives in one chunk. - DCHECK_GE(current_frame_header_->payload_length, - base::checked_numeric_cast<uint64>(data_buffer->size())); - DCHECK(!is_first_chunk || !is_final_chunk || - current_frame_header_->payload_length == - base::checked_numeric_cast<uint64>(data_buffer->size())); - // Respond to the frame appropriately to its type. - HandleFrame(opcode, is_first_chunk, is_final_chunk, data_buffer); - - if (is_final_chunk) { - // Make sure that this frame header is not applied to any future chunks. - current_frame_header_.reset(); - } -} - -void WebSocketChannel::AddToIncompleteControlFrameBody( - const scoped_refptr<IOBufferWithSize>& data_buffer) { - const int new_offset = - incomplete_control_frame_body_->offset() + data_buffer->size(); - CHECK_GE(incomplete_control_frame_body_->capacity(), new_offset) - << "Control frame body larger than frame header indicates; frame parser " - "bug?"; - memcpy(incomplete_control_frame_body_->data(), - data_buffer->data(), - data_buffer->size()); - incomplete_control_frame_body_->set_offset(new_offset); + HandleFrame( + opcode, frame->header.final, frame->data, frame->header.payload_length); } -void WebSocketChannel::HandleFrame( - const WebSocketFrameHeader::OpCode opcode, - bool is_first_chunk, - bool is_final_chunk, - const scoped_refptr<IOBufferWithSize>& data_buffer) { +void WebSocketChannel::HandleFrame(const WebSocketFrameHeader::OpCode opcode, + bool final, + const scoped_refptr<IOBuffer>& data_buffer, + size_t size) { DCHECK_NE(RECV_CLOSED, state_) << "HandleFrame() does not support being called re-entrantly from within " "SendClose()"; @@ -513,12 +429,11 @@ void WebSocketChannel::HandleFrame( case WebSocketFrameHeader::kOpCodeBinary: // fall-thru case WebSocketFrameHeader::kOpCodeContinuation: if (state_ == CONNECTED) { - const bool final = is_final_chunk && current_frame_header_->final; // TODO(ricea): Need to fail the connection if UTF-8 is invalid // post-reassembly. Requires a streaming UTF-8 validator. // TODO(ricea): Can this copy be eliminated? const char* const data_begin = data_buffer->data(); - const char* const data_end = data_begin + data_buffer->size(); + const char* const data_end = data_begin + size; const std::vector<char> data(data_begin, data_end); // TODO(ricea): Handle the case when ReadFrames returns far // more data at once than should be sent in a single IPC. This needs to @@ -526,34 +441,31 @@ void WebSocketChannel::HandleFrame( // cause of receiving very large chunks. // Sends the received frame to the renderer process. - event_interface_->OnDataFrame( - final, - is_first_chunk ? opcode : WebSocketFrameHeader::kOpCodeContinuation, - data); + event_interface_->OnDataFrame(final, opcode, data); } else { VLOG(3) << "Ignored data packet received in state " << state_; } return; case WebSocketFrameHeader::kOpCodePing: - VLOG(1) << "Got Ping of size " << data_buffer->size(); + VLOG(1) << "Got Ping of size " << size; if (state_ == CONNECTED) { - SendIOBufferWithSize( - true, WebSocketFrameHeader::kOpCodePong, data_buffer); + SendIOBuffer( + true, WebSocketFrameHeader::kOpCodePong, data_buffer, size); } else { VLOG(3) << "Ignored ping in state " << state_; } return; case WebSocketFrameHeader::kOpCodePong: - VLOG(1) << "Got Pong of size " << data_buffer->size(); + VLOG(1) << "Got Pong of size " << size; // There is no need to do anything with pong messages. return; case WebSocketFrameHeader::kOpCodeClose: { uint16 code = kWebSocketNormalClosure; std::string reason; - ParseClose(data_buffer, &code, &reason); + ParseClose(data_buffer, size, &code, &reason); // TODO(ricea): Find a way to safely log the message from the close // message (escape control codes and so on). VLOG(1) << "Got Close with code " << code; @@ -589,20 +501,18 @@ void WebSocketChannel::HandleFrame( } } -void WebSocketChannel::SendIOBufferWithSize( - bool fin, - WebSocketFrameHeader::OpCode op_code, - const scoped_refptr<IOBufferWithSize>& buffer) { +void WebSocketChannel::SendIOBuffer(bool fin, + WebSocketFrameHeader::OpCode op_code, + const scoped_refptr<IOBuffer>& buffer, + size_t size) { DCHECK(state_ == CONNECTED || state_ == RECV_CLOSED); DCHECK(stream_); - scoped_ptr<WebSocketFrameHeader> header(new WebSocketFrameHeader(op_code)); - header->final = fin; - header->masked = true; - header->payload_length = buffer->size(); - scoped_ptr<WebSocketFrameChunk> chunk(new WebSocketFrameChunk()); - chunk->header = header.Pass(); - chunk->final_chunk = true; - chunk->data = buffer; + scoped_ptr<WebSocketFrame> frame(new WebSocketFrame(op_code)); + WebSocketFrameHeader& header = frame->header; + header.final = fin; + header.masked = true; + header.payload_length = size; + frame->data = buffer; if (data_being_sent_) { // Either the link to the WebSocket server is saturated, or several messages // are being sent in a batch. @@ -610,10 +520,10 @@ void WebSocketChannel::SendIOBufferWithSize( // quota appropriately. if (!data_to_send_next_) data_to_send_next_.reset(new SendBuffer); - data_to_send_next_->AddFrame(chunk.Pass()); + data_to_send_next_->AddFrame(frame.Pass()); } else { data_being_sent_.reset(new SendBuffer); - data_being_sent_->AddFrame(chunk.Pass()); + data_being_sent_->AddFrame(frame.Pass()); WriteFrames(); } } @@ -640,9 +550,6 @@ void WebSocketChannel::FailChannel(ExposeError expose, stream_->Close(); state_ = CLOSED; - // The channel may be in the middle of processing several chunks. It should - // not use this frame header for subsequent chunks. - current_frame_header_.reset(); if (old_state != CLOSED) { event_interface_->OnDropChannel(code, reason); } @@ -651,29 +558,31 @@ void WebSocketChannel::FailChannel(ExposeError expose, void WebSocketChannel::SendClose(uint16 code, const std::string& reason) { DCHECK(state_ == CONNECTED || state_ == RECV_CLOSED); // TODO(ricea): Ensure reason.length() <= 123 - scoped_refptr<IOBufferWithSize> body; + scoped_refptr<IOBuffer> body; + size_t size = 0; if (code == kWebSocketErrorNoStatusReceived) { // Special case: translate kWebSocketErrorNoStatusReceived into a Close // frame with no payload. - body = new IOBufferWithSize(0); + body = new IOBuffer(0); } else { const size_t payload_length = kWebSocketCloseCodeLength + reason.length(); - body = new IOBufferWithSize(payload_length); + body = new IOBuffer(payload_length); + size = payload_length; WriteBigEndian(body->data(), code); COMPILE_ASSERT(sizeof(code) == kWebSocketCloseCodeLength, they_should_both_be_two); std::copy( reason.begin(), reason.end(), body->data() + kWebSocketCloseCodeLength); } - SendIOBufferWithSize(true, WebSocketFrameHeader::kOpCodeClose, body); + SendIOBuffer(true, WebSocketFrameHeader::kOpCodeClose, body, size); state_ = (state_ == CONNECTED) ? SEND_CLOSED : CLOSE_WAIT; } -void WebSocketChannel::ParseClose(const scoped_refptr<IOBufferWithSize>& buffer, +void WebSocketChannel::ParseClose(const scoped_refptr<IOBuffer>& buffer, + size_t size, uint16* code, std::string* reason) { const char* data = buffer->data(); - size_t size = base::checked_numeric_cast<size_t>(buffer->size()); reason->clear(); if (size < kWebSocketCloseCodeLength) { *code = kWebSocketErrorNoStatusReceived; diff --git a/net/websockets/websocket_channel.h b/net/websockets/websocket_channel.h index c997d6a..dda30cd 100644 --- a/net/websockets/websocket_channel.h +++ b/net/websockets/websocket_channel.h @@ -19,10 +19,10 @@ namespace net { -class GrowableIOBuffer; +class BoundNetLog; +class IOBuffer; class URLRequestContext; class WebSocketEventInterface; -class BoundNetLog; // Transport-independent implementation of WebSockets. Implements protocol // semantics that do not depend on the underlying transport. Provides the @@ -164,37 +164,33 @@ class NET_EXPORT WebSocketChannel { // within the ReadFrames() loop and does not need to call ReadFrames() itself. void OnReadDone(bool synchronous, int result); - // Processes a single chunk that has been read from the stream. - void ProcessFrameChunk(scoped_ptr<WebSocketFrameChunk> chunk); - - // Appends |data_buffer| to |incomplete_control_frame_body_|. - void AddToIncompleteControlFrameBody( - const scoped_refptr<IOBufferWithSize>& data_buffer); + // Processes a single frame that has been read from the stream. + void ProcessFrame(scoped_ptr<WebSocketFrame> frame); // Handles a frame that the object has received enough of to process. May call - // event_interface_ methods, send responses to the server, and change the - // value of state_. + // |event_interface_| methods, send responses to the server, and change the + // value of |state_|. void HandleFrame(const WebSocketFrameHeader::OpCode opcode, - bool is_first_chunk, - bool is_final_chunk, - const scoped_refptr<IOBufferWithSize>& data_buffer); + bool final, + const scoped_refptr<IOBuffer>& data_buffer, + size_t size); // Low-level method to send a single frame. Used for both data and control // frames. Either sends the frame immediately or buffers it to be scheduled // when the current write finishes. |fin| and |op_code| are defined as for // SendFrame() above, except that |op_code| may also be a control frame // opcode. - void SendIOBufferWithSize(bool fin, - WebSocketFrameHeader::OpCode op_code, - const scoped_refptr<IOBufferWithSize>& buffer); + void SendIOBuffer(bool fin, + WebSocketFrameHeader::OpCode op_code, + const scoped_refptr<IOBuffer>& buffer, + size_t size); // Performs the "Fail the WebSocket Connection" operation as defined in // RFC6455. The supplied code and reason are sent back to the renderer in an // OnDropChannel message. If state_ is CONNECTED then a Close message is sent // to the remote host. If |expose| is SEND_REAL_ERROR then the remote host is // given the same status code passed to the renderer; otherwise it is sent a - // fixed "Going Away" code. Resets current_frame_header_, closes the stream_, - // and sets state_ to CLOSED. + // fixed "Going Away" code. Closes the stream_ and sets state_ to CLOSED. void FailChannel(ExposeError expose, uint16 code, const std::string& reason); // Sends a Close frame to Start the WebSocket Closing Handshake, or to respond @@ -208,7 +204,8 @@ class NET_EXPORT WebSocketChannel { // outside the valid range, then 1002 (Protocol error) is set instead. If the // reason text is not valid UTF-8, then |reason| is set to an empty string // instead. - void ParseClose(const scoped_refptr<IOBufferWithSize>& buffer, + void ParseClose(const scoped_refptr<IOBuffer>& buffer, + size_t size, uint16* code, std::string* reason); @@ -231,20 +228,12 @@ class NET_EXPORT WebSocketChannel { scoped_ptr<SendBuffer> data_to_send_next_; // Destination for the current call to WebSocketStream::ReadFrames - ScopedVector<WebSocketFrameChunk> read_frame_chunks_; - // Frame header for the frame currently being received. Only non-NULL while we - // are processing the frame. If the frame arrives in multiple chunks, it can - // remain non-NULL until additional chunks arrive. If the header of the frame - // was invalid, this is set to NULL, the channel is failed, and subsequent - // chunks of the same frame will be ignored. - scoped_ptr<WebSocketFrameHeader> current_frame_header_; + ScopedVector<WebSocketFrame> read_frames_; + // Handle to an in-progress WebSocketStream creation request. Only non-NULL // during the connection process. scoped_ptr<WebSocketStreamRequest> stream_request_; - // Although it should rarely happen in practice, a control frame can arrive - // broken into chunks. This variable provides storage for a partial control - // frame until the rest arrives. It will be NULL the rest of the time. - scoped_refptr<GrowableIOBuffer> incomplete_control_frame_body_; + // If the renderer's send quota reaches this level, it is sent a quota // refresh. "quota units" are currently bytes. TODO(ricea): Update the // definition of quota units when necessary. diff --git a/net/websockets/websocket_channel_test.cc b/net/websockets/websocket_channel_test.cc index 25e9cdc..361f69f 100644 --- a/net/websockets/websocket_channel_test.cc +++ b/net/websockets/websocket_channel_test.cc @@ -34,40 +34,37 @@ #define CLOSE_DATA(code, string) WEBSOCKET_CLOSE_CODE_AS_STRING_##code string #define WEBSOCKET_CLOSE_CODE_AS_STRING_NORMAL_CLOSURE "\x03\xe8" #define WEBSOCKET_CLOSE_CODE_AS_STRING_GOING_AWAY "\x03\xe9" +#define WEBSOCKET_CLOSE_CODE_AS_STRING_PROTOCOL_ERROR "\x03\xea" #define WEBSOCKET_CLOSE_CODE_AS_STRING_SERVER_ERROR "\x03\xf3" namespace net { -// Printing helpers to allow GoogleMock to print frame chunks. These are -// explicitly designed to look like the static initialisation format we use in -// these tests. They have to live in the net namespace in order to be found by +// Printing helpers to allow GoogleMock to print frames. These are explicitly +// designed to look like the static initialisation format we use in these +// tests. They have to live in the net namespace in order to be found by // GoogleMock; a nested anonymous namespace will not work. std::ostream& operator<<(std::ostream& os, const WebSocketFrameHeader& header) { - return os << "{" << (header.final ? "FINAL_FRAME" : "NOT_FINAL_FRAME") << ", " + return os << (header.final ? "FINAL_FRAME" : "NOT_FINAL_FRAME") << ", " << header.opcode << ", " - << (header.masked ? "MASKED" : "NOT_MASKED") << ", " - << header.payload_length << "}"; + << (header.masked ? "MASKED" : "NOT_MASKED"); } -std::ostream& operator<<(std::ostream& os, const WebSocketFrameChunk& chunk) { - os << "{"; - if (chunk.header) { - os << *chunk.header; - } else { - os << "{NO_HEADER}"; +std::ostream& operator<<(std::ostream& os, const WebSocketFrame& frame) { + os << "{" << frame.header << ", "; + if (frame.data) { + return os << "\"" << base::StringPiece(frame.data->data(), + frame.header.payload_length) + << "\"}"; } - return os << ", " << (chunk.final_chunk ? "FINAL_CHUNK" : "NOT_FINAL_CHUNK") - << ", \"" - << base::StringPiece(chunk.data->data(), chunk.data->size()) - << "\"}"; + return os << "NULL}"; } std::ostream& operator<<(std::ostream& os, - const ScopedVector<WebSocketFrameChunk>& vector) { + const ScopedVector<WebSocketFrame>& vector) { os << "{"; bool first = true; - for (ScopedVector<WebSocketFrameChunk>::const_iterator it = vector.begin(); + for (ScopedVector<WebSocketFrame>::const_iterator it = vector.begin(); it != vector.end(); ++it) { if (!first) { @@ -81,7 +78,7 @@ std::ostream& operator<<(std::ostream& os, } std::ostream& operator<<(std::ostream& os, - const ScopedVector<WebSocketFrameChunk>* vector) { + const ScopedVector<WebSocketFrame>* vector) { return os << '&' << *vector; } @@ -97,7 +94,7 @@ using ::testing::_; // A selection of characters that have traditionally been mangled in some // environment or other, for testing 8-bit cleanliness. -const char kBinaryBlob[] = {'\n', '\r', // BACKWARDS CRNL +const char kBinaryBlob[] = {'\n', '\r', // BACKWARDS CRNL '\0', // nul '\x7F', // DEL '\x80', '\xFF', // NOT VALID UTF-8 @@ -170,12 +167,12 @@ class FakeWebSocketStream : public WebSocketStream { return ERR_IO_PENDING; } - virtual int ReadFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks, + virtual int ReadFrames(ScopedVector<WebSocketFrame>* frames, const CompletionCallback& callback) OVERRIDE { return ERR_IO_PENDING; } - virtual int WriteFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks, + virtual int WriteFrames(ScopedVector<WebSocketFrame>* frames, const CompletionCallback& callback) OVERRIDE { return ERR_IO_PENDING; } @@ -198,12 +195,7 @@ class FakeWebSocketStream : public WebSocketStream { // To make the static initialisers easier to read, we use enums rather than // bools. - -// NO_HEADER means there shouldn't be a header included in the generated -// WebSocketFrameChunk. The static initialiser always has a header, but we can -// avoid specifying the rest of the fields. enum IsFinal { - NO_HEADER, NOT_FINAL_FRAME, FINAL_FRAME }; @@ -213,54 +205,33 @@ enum IsMasked { MASKED }; -enum IsFinalChunk { - NOT_FINAL_CHUNK, - FINAL_CHUNK -}; - -// This is used to initialise a WebSocketFrameChunk but is statically -// initialisable. -struct InitFrameChunk { - struct FrameHeader { - IsFinal final; - // Reserved fields omitted for now. Add them if you need them. - WebSocketFrameHeader::OpCode opcode; - IsMasked masked; - // payload_length is the length of the whole frame. The length of the data - // members from every chunk in the frame must add up to the payload_length. - uint64 payload_length; - }; - FrameHeader header; - - // Directly equivalent to WebSocketFrameChunk::final_chunk - IsFinalChunk final_chunk; +// This is used to initialise a WebSocketFrame but is statically initialisable. +struct InitFrame { + IsFinal final; + // Reserved fields omitted for now. Add them if you need them. + WebSocketFrameHeader::OpCode opcode; + IsMasked masked; // Will be used to create the IOBuffer member. Can be NULL for NULL data. Is a - // nul-terminated string for ease-of-use. This means it is not 8-bit clean, - // but this is not an issue for test data. + // nul-terminated string for ease-of-use. |header.payload_length| is + // initialised from |strlen(data)|. This means it is not 8-bit clean, but this + // is not an issue for test data. const char* const data; }; // For GoogleMock -std::ostream& operator<<(std::ostream& os, const InitFrameChunk& chunk) { - os << "{"; - if (chunk.header.final != NO_HEADER) { - os << "{" << (chunk.header.final == FINAL_FRAME ? "FINAL_FRAME" - : "NOT_FINAL_FRAME") << ", " - << chunk.header.opcode << ", " - << (chunk.header.masked == MASKED ? "MASKED" : "NOT_MASKED") << ", " - << chunk.header.payload_length << "}"; - - } else { - os << "{NO_HEADER}"; +std::ostream& operator<<(std::ostream& os, const InitFrame& frame) { + os << "{" << (frame.final == FINAL_FRAME ? "FINAL_FRAME" : "NOT_FINAL_FRAME") + << ", " << frame.opcode << ", " + << (frame.masked == MASKED ? "MASKED" : "NOT_MASKED") << ", "; + if (frame.data) { + return os << "\"" << frame.data << "\"}"; } - return os << ", " << (chunk.final_chunk == FINAL_CHUNK ? "FINAL_CHUNK" - : "NOT_FINAL_CHUNK") - << ", \"" << chunk.data << "\"}"; + return os << "NULL}"; } template <size_t N> -std::ostream& operator<<(std::ostream& os, const InitFrameChunk (&chunks)[N]) { +std::ostream& operator<<(std::ostream& os, const InitFrame (&frames)[N]) { os << "{"; bool first = true; for (size_t i = 0; i < N; ++i) { @@ -269,119 +240,92 @@ std::ostream& operator<<(std::ostream& os, const InitFrameChunk (&chunks)[N]) { } else { first = false; } - os << chunks[i]; + os << frames[i]; } return os << "}"; } -// Convert a const array of InitFrameChunks to the format used at +// Convert a const array of InitFrame structs to the format used at // runtime. Templated on the size of the array to save typing. template <size_t N> -ScopedVector<WebSocketFrameChunk> CreateFrameChunkVector( - const InitFrameChunk (&source_chunks)[N]) { - ScopedVector<WebSocketFrameChunk> result_chunks; - result_chunks.reserve(N); +ScopedVector<WebSocketFrame> CreateFrameVector( + const InitFrame (&source_frames)[N]) { + ScopedVector<WebSocketFrame> result_frames; + result_frames.reserve(N); for (size_t i = 0; i < N; ++i) { - scoped_ptr<WebSocketFrameChunk> result_chunk(new WebSocketFrameChunk); - size_t chunk_length = - source_chunks[i].data ? strlen(source_chunks[i].data) : 0; - if (source_chunks[i].header.final != NO_HEADER) { - const InitFrameChunk::FrameHeader& source_header = - source_chunks[i].header; - scoped_ptr<WebSocketFrameHeader> result_header( - new WebSocketFrameHeader(source_header.opcode)); - result_header->final = (source_header.final == FINAL_FRAME); - result_header->masked = (source_header.masked == MASKED); - result_header->payload_length = source_header.payload_length; - DCHECK(chunk_length <= source_header.payload_length); - result_chunk->header.swap(result_header); - } - result_chunk->final_chunk = (source_chunks[i].final_chunk == FINAL_CHUNK); - if (source_chunks[i].data) { - result_chunk->data = new IOBufferWithSize(chunk_length); - memcpy(result_chunk->data->data(), source_chunks[i].data, chunk_length); + const InitFrame& source_frame = source_frames[i]; + scoped_ptr<WebSocketFrame> result_frame( + new WebSocketFrame(source_frame.opcode)); + size_t frame_length = source_frame.data ? strlen(source_frame.data) : 0; + WebSocketFrameHeader& result_header = result_frame->header; + result_header.final = (source_frame.final == FINAL_FRAME); + result_header.masked = (source_frame.masked == MASKED); + result_header.payload_length = frame_length; + if (source_frame.data) { + result_frame->data = new IOBuffer(frame_length); + memcpy(result_frame->data->data(), source_frame.data, frame_length); } - result_chunks.push_back(result_chunk.release()); + result_frames.push_back(result_frame.release()); } - return result_chunks.Pass(); + return result_frames.Pass(); } // A GoogleMock action which can be used to respond to call to ReadFrames with -// some frames. Use like ReadFrames(_, _).WillOnce(ReturnChunks(&chunks)); -// |chunks| is an array of InitFrameChunks needs to be passed by pointer because -// otherwise it will be reduced to a pointer and lose the array size -// information. -ACTION_P(ReturnChunks, source_chunks) { - *arg0 = CreateFrameChunkVector(*source_chunks); +// some frames. Use like ReadFrames(_, _).WillOnce(ReturnFrames(&frames)); +// |frames| is an array of InitFrame. |frames| needs to be passed by pointer +// because otherwise it will be treated as a pointer and the array size +// information will be lost. +ACTION_P(ReturnFrames, source_frames) { + *arg0 = CreateFrameVector(*source_frames); return OK; } // The implementation of a GoogleMock matcher which can be used to compare a -// ScopedVector<WebSocketFrameChunk>* against an expectation defined as an array -// of InitFrameChunks. Although it is possible to compose built-in GoogleMock -// matchers to check the contents of a WebSocketFrameChunk, the results are so +// ScopedVector<WebSocketFrame>* against an expectation defined as an array of +// InitFrame objects. Although it is possible to compose built-in GoogleMock +// matchers to check the contents of a WebSocketFrame, the results are so // unreadable that it is better to use this matcher. template <size_t N> -class EqualsChunksMatcher - : public ::testing::MatcherInterface<ScopedVector<WebSocketFrameChunk>*> { +class EqualsFramesMatcher + : public ::testing::MatcherInterface<ScopedVector<WebSocketFrame>*> { public: - EqualsChunksMatcher(const InitFrameChunk (*expect_chunks)[N]) - : expect_chunks_(expect_chunks) {} + EqualsFramesMatcher(const InitFrame (*expect_frames)[N]) + : expect_frames_(expect_frames) {} - virtual bool MatchAndExplain(ScopedVector<WebSocketFrameChunk>* actual_chunks, + virtual bool MatchAndExplain(ScopedVector<WebSocketFrame>* actual_frames, ::testing::MatchResultListener* listener) const { - if (actual_chunks->size() != N) { - *listener << "the vector size is " << actual_chunks->size(); + if (actual_frames->size() != N) { + *listener << "the vector size is " << actual_frames->size(); return false; } for (size_t i = 0; i < N; ++i) { - const WebSocketFrameChunk& actual_chunk = *(*actual_chunks)[i]; - const InitFrameChunk& expected_chunk = (*expect_chunks_)[i]; - // Testing that the absence or presence of a header is the same for both. - if ((!actual_chunk.header) != - (expected_chunk.header.final == NO_HEADER)) { - *listener << "the header is " - << (actual_chunk.header ? "present" : "absent"); + const WebSocketFrame& actual_frame = *(*actual_frames)[i]; + const InitFrame& expected_frame = (*expect_frames_)[i]; + if (actual_frame.header.final != (expected_frame.final == FINAL_FRAME)) { + *listener << "the frame is marked as " + << (actual_frame.header.final ? "" : "not ") << "final"; return false; } - if (actual_chunk.header) { - if (actual_chunk.header->final != - (expected_chunk.header.final == FINAL_FRAME)) { - *listener << "the frame is marked as " - << (actual_chunk.header->final ? "" : "not ") << "final"; - return false; - } - if (actual_chunk.header->opcode != expected_chunk.header.opcode) { - *listener << "the opcode is " << actual_chunk.header->opcode; - return false; - } - if (actual_chunk.header->masked != - (expected_chunk.header.masked == MASKED)) { - *listener << "the frame is " - << (actual_chunk.header->masked ? "masked" : "not masked"); - return false; - } - if (actual_chunk.header->payload_length != - expected_chunk.header.payload_length) { - *listener << "the payload length is " - << actual_chunk.header->payload_length; - return false; - } + if (actual_frame.header.opcode != expected_frame.opcode) { + *listener << "the opcode is " << actual_frame.header.opcode; + return false; } - if (actual_chunk.final_chunk != - (expected_chunk.final_chunk == FINAL_CHUNK)) { - *listener << "the chunk is marked as " - << (actual_chunk.final_chunk ? "" : "not ") << "final"; + if (actual_frame.header.masked != (expected_frame.masked == MASKED)) { + *listener << "the frame is " + << (actual_frame.header.masked ? "masked" : "not masked"); return false; } - if (actual_chunk.data->size() != - base::checked_numeric_cast<int>(strlen(expected_chunk.data))) { - *listener << "the data size is " << actual_chunk.data->size(); + const size_t expected_length = + expected_frame.data ? strlen(expected_frame.data) : 0; + if (actual_frame.header.payload_length != expected_length) { + *listener << "the payload length is " + << actual_frame.header.payload_length; return false; } - if (memcmp(actual_chunk.data->data(), - expected_chunk.data, - actual_chunk.data->size()) != 0) { + if (expected_length != 0 && + memcmp(actual_frame.data->data(), + expected_frame.data, + actual_frame.header.payload_length) != 0) { *listener << "the data content differs"; return false; } @@ -390,23 +334,23 @@ class EqualsChunksMatcher } virtual void DescribeTo(std::ostream* os) const { - *os << "matches " << *expect_chunks_; + *os << "matches " << *expect_frames_; } virtual void DescribeNegationTo(std::ostream* os) const { - *os << "does not match " << *expect_chunks_; + *os << "does not match " << *expect_frames_; } private: - const InitFrameChunk (*expect_chunks_)[N]; + const InitFrame (*expect_frames_)[N]; }; -// The definition of EqualsChunks GoogleMock matcher. Unlike the ReturnChunks +// The definition of EqualsFrames GoogleMock matcher. Unlike the ReturnFrames // action, this can take the array by reference. template <size_t N> -::testing::Matcher<ScopedVector<WebSocketFrameChunk>*> EqualsChunks( - const InitFrameChunk (&chunks)[N]) { - return ::testing::MakeMatcher(new EqualsChunksMatcher<N>(&chunks)); +::testing::Matcher<ScopedVector<WebSocketFrame>*> EqualsFrames( + const InitFrame (&frames)[N]) { + return ::testing::MakeMatcher(new EqualsFramesMatcher<N>(&frames)); } // A FakeWebSocketStream whose ReadFrames() function returns data. @@ -427,39 +371,38 @@ class ReadableFakeWebSocketStream : public FakeWebSocketStream { CHECK(!read_frames_pending_); } - // Prepares a fake responses. Fake responses will be returned from - // ReadFrames() in the same order they were prepared with PrepareReadFrames() - // and PrepareReadFramesError(). If |async| is ASYNC, then ReadFrames() will + // Prepares a fake response. Fake responses will be returned from ReadFrames() + // in the same order they were prepared with PrepareReadFrames() and + // PrepareReadFramesError(). If |async| is ASYNC, then ReadFrames() will // return ERR_IO_PENDING and the callback will be scheduled to run on the // message loop. This requires the test case to run the message loop. If // |async| is SYNC, the response will be returned synchronously. |error| is // returned directly from ReadFrames() in the synchronous case, or passed to - // the callback in the asynchronous case. |chunks| will be converted to a - // ScopedVector<WebSocketFrameChunks> and copied to the pointer that was - // passed to ReadFrames(). + // the callback in the asynchronous case. |frames| will be converted to a + // ScopedVector<WebSocketFrame> and copied to the pointer that was passed to + // ReadFrames(). template <size_t N> void PrepareReadFrames(IsSync async, int error, - const InitFrameChunk (&chunks)[N]) { - responses_.push_back( - new Response(async, error, CreateFrameChunkVector(chunks))); + const InitFrame (&frames)[N]) { + responses_.push_back(new Response(async, error, CreateFrameVector(frames))); } // An alternate version of PrepareReadFrames for when we need to construct // the frames manually. void PrepareRawReadFrames(IsSync async, int error, - ScopedVector<WebSocketFrameChunk> chunks) { - responses_.push_back(new Response(async, error, chunks.Pass())); + ScopedVector<WebSocketFrame> frames) { + responses_.push_back(new Response(async, error, frames.Pass())); } // Prepares a fake error response (ie. there is no data). void PrepareReadFramesError(IsSync async, int error) { responses_.push_back( - new Response(async, error, ScopedVector<WebSocketFrameChunk>())); + new Response(async, error, ScopedVector<WebSocketFrame>())); } - virtual int ReadFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks, + virtual int ReadFrames(ScopedVector<WebSocketFrame>* frames, const CompletionCallback& callback) OVERRIDE { CHECK(!read_frames_pending_); if (index_ >= responses_.size()) @@ -470,34 +413,34 @@ class ReadableFakeWebSocketStream : public FakeWebSocketStream { FROM_HERE, base::Bind(&ReadableFakeWebSocketStream::DoCallback, base::Unretained(this), - frame_chunks, + frames, callback)); return ERR_IO_PENDING; } else { - frame_chunks->swap(responses_[index_]->chunks); + frames->swap(responses_[index_]->frames); return responses_[index_++]->error; } } private: - void DoCallback(ScopedVector<WebSocketFrameChunk>* frame_chunks, + void DoCallback(ScopedVector<WebSocketFrame>* frames, const CompletionCallback& callback) { read_frames_pending_ = false; - frame_chunks->swap(responses_[index_]->chunks); + frames->swap(responses_[index_]->frames); callback.Run(responses_[index_++]->error); return; } struct Response { - Response(IsSync async, int error, ScopedVector<WebSocketFrameChunk> chunks) - : async(async), error(error), chunks(chunks.Pass()) {} + Response(IsSync async, int error, ScopedVector<WebSocketFrame> frames) + : async(async), error(error), frames(frames.Pass()) {} IsSync async; int error; - ScopedVector<WebSocketFrameChunk> chunks; + ScopedVector<WebSocketFrame> frames; private: - // Bad things will happen if we attempt to copy or assign "chunks". + // Bad things will happen if we attempt to copy or assign |frames|. DISALLOW_COPY_AND_ASSIGN(Response); }; ScopedVector<Response> responses_; @@ -516,7 +459,7 @@ class ReadableFakeWebSocketStream : public FakeWebSocketStream { // synchronously. class WriteableFakeWebSocketStream : public FakeWebSocketStream { public: - virtual int WriteFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks, + virtual int WriteFrames(ScopedVector<WebSocketFrame>* frames, const CompletionCallback& callback) OVERRIDE { return OK; } @@ -525,7 +468,7 @@ class WriteableFakeWebSocketStream : public FakeWebSocketStream { // A FakeWebSocketStream where writes always fail. class UnWriteableFakeWebSocketStream : public FakeWebSocketStream { public: - virtual int WriteFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks, + virtual int WriteFrames(ScopedVector<WebSocketFrame>* frames, const CompletionCallback& callback) OVERRIDE { return ERR_CONNECTION_RESET; } @@ -539,23 +482,22 @@ class UnWriteableFakeWebSocketStream : public FakeWebSocketStream { // otherwise the ReadFrames() callback will never be called. class EchoeyFakeWebSocketStream : public FakeWebSocketStream { public: - EchoeyFakeWebSocketStream() : read_frame_chunks_(NULL), done_(false) {} + EchoeyFakeWebSocketStream() : read_frames_(NULL), done_(false) {} - virtual int WriteFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks, + virtual int WriteFrames(ScopedVector<WebSocketFrame>* frames, const CompletionCallback& callback) OVERRIDE { // Users of WebSocketStream will not expect the ReadFrames() callback to be // called from within WriteFrames(), so post it to the message loop instead. - stored_frame_chunks_.insert( - stored_frame_chunks_.end(), frame_chunks->begin(), frame_chunks->end()); - frame_chunks->weak_clear(); + stored_frames_.insert(stored_frames_.end(), frames->begin(), frames->end()); + frames->weak_clear(); PostCallback(); return OK; } - virtual int ReadFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks, + virtual int ReadFrames(ScopedVector<WebSocketFrame>* frames, const CompletionCallback& callback) OVERRIDE { read_callback_ = callback; - read_frame_chunks_ = frame_chunks; + read_frames_ = frames; if (done_) PostCallback(); return ERR_IO_PENDING; @@ -572,36 +514,34 @@ class EchoeyFakeWebSocketStream : public FakeWebSocketStream { void DoCallback() { if (done_) { read_callback_.Run(ERR_CONNECTION_CLOSED); - } else if (!stored_frame_chunks_.empty()) { - done_ = MoveFrameChunks(read_frame_chunks_); - read_frame_chunks_ = NULL; + } else if (!stored_frames_.empty()) { + done_ = MoveFrames(read_frames_); + read_frames_ = NULL; read_callback_.Run(OK); } } - // Copy the chunks stored in stored_frame_chunks_ to |out|, while clearing the + // Copy the frames stored in stored_frames_ to |out|, while clearing the // "masked" header bit. Returns true if a Close Frame was seen, false // otherwise. - bool MoveFrameChunks(ScopedVector<WebSocketFrameChunk>* out) { + bool MoveFrames(ScopedVector<WebSocketFrame>* out) { bool seen_close = false; - *out = stored_frame_chunks_.Pass(); - for (ScopedVector<WebSocketFrameChunk>::iterator it = out->begin(); + *out = stored_frames_.Pass(); + for (ScopedVector<WebSocketFrame>::iterator it = out->begin(); it != out->end(); ++it) { - WebSocketFrameHeader* header = (*it)->header.get(); - if (header) { - header->masked = false; - if (header->opcode == WebSocketFrameHeader::kOpCodeClose) - seen_close = true; - } + WebSocketFrameHeader& header = (*it)->header; + header.masked = false; + if (header.opcode == WebSocketFrameHeader::kOpCodeClose) + seen_close = true; } return seen_close; } - ScopedVector<WebSocketFrameChunk> stored_frame_chunks_; + ScopedVector<WebSocketFrame> stored_frames_; CompletionCallback read_callback_; // Owned by the caller of ReadFrames(). - ScopedVector<WebSocketFrameChunk>* read_frame_chunks_; + ScopedVector<WebSocketFrame>* read_frames_; // True if we should close the connection. bool done_; }; @@ -612,7 +552,7 @@ class EchoeyFakeWebSocketStream : public FakeWebSocketStream { // run the message loop. class ResetOnWriteFakeWebSocketStream : public FakeWebSocketStream { public: - virtual int WriteFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks, + virtual int WriteFrames(ScopedVector<WebSocketFrame>* frames, const CompletionCallback& callback) OVERRIDE { base::MessageLoop::current()->PostTask( FROM_HERE, base::Bind(callback, ERR_CONNECTION_RESET)); @@ -621,7 +561,7 @@ class ResetOnWriteFakeWebSocketStream : public FakeWebSocketStream { return ERR_IO_PENDING; } - virtual int ReadFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks, + virtual int ReadFrames(ScopedVector<WebSocketFrame>* frames, const CompletionCallback& callback) OVERRIDE { read_callback_ = callback; return ERR_IO_PENDING; @@ -636,10 +576,10 @@ class ResetOnWriteFakeWebSocketStream : public FakeWebSocketStream { class MockWebSocketStream : public WebSocketStream { public: MOCK_METHOD2(ReadFrames, - int(ScopedVector<WebSocketFrameChunk>* frame_chunks, + int(ScopedVector<WebSocketFrame>* frames, const CompletionCallback& callback)); MOCK_METHOD2(WriteFrames, - int(ScopedVector<WebSocketFrameChunk>* frame_chunks, + int(ScopedVector<WebSocketFrame>* frames, const CompletionCallback& callback)); MOCK_METHOD0(Close, void()); MOCK_CONST_METHOD0(GetSubProtocol, std::string()); @@ -846,8 +786,8 @@ TEST_F(WebSocketChannelTest, EverythingIsPassedToTheFactoryFunction) { // AddressSanitizer. TEST_F(WebSocketChannelDeletingTest, DeletingFromOnAddChannelResponseWorks) { CreateChannelAndConnect(); - connect_data_.factory.connect_delegate - ->OnFailure(kWebSocketErrorNoStatusReceived); + connect_data_.factory.connect_delegate->OnFailure( + kWebSocketErrorNoStatusReceived); EXPECT_EQ(NULL, channel_.get()); } @@ -869,8 +809,8 @@ TEST_F(WebSocketChannelEventInterfaceTest, ConnectFailureReported) { CreateChannelAndConnect(); - connect_data_.factory.connect_delegate - ->OnFailure(kWebSocketErrorNoStatusReceived); + connect_data_.factory.connect_delegate->OnFailure( + kWebSocketErrorNoStatusReceived); } TEST_F(WebSocketChannelEventInterfaceTest, ProtocolPassed) { @@ -889,10 +829,9 @@ TEST_F(WebSocketChannelEventInterfaceTest, ProtocolPassed) { TEST_F(WebSocketChannelEventInterfaceTest, DataLeftFromHandshake) { scoped_ptr<ReadableFakeWebSocketStream> stream( new ReadableFakeWebSocketStream); - static const InitFrameChunk chunks[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, 5}, - FINAL_CHUNK, "HELLO"}}; - stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, chunks); + static const InitFrame frames[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, "HELLO"}}; + stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, frames); set_stream(stream.Pass()); { InSequence s; @@ -912,10 +851,10 @@ TEST_F(WebSocketChannelEventInterfaceTest, DataLeftFromHandshake) { TEST_F(WebSocketChannelEventInterfaceTest, CloseAfterHandshake) { scoped_ptr<ReadableFakeWebSocketStream> stream( new ReadableFakeWebSocketStream); - static const InitFrameChunk chunks[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, 23}, - FINAL_CHUNK, CLOSE_DATA(SERVER_ERROR, "Internal Server Error")}}; - stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, chunks); + static const InitFrame frames[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, + NOT_MASKED, CLOSE_DATA(SERVER_ERROR, "Internal Server Error")}}; + stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, frames); stream->PrepareReadFramesError(ReadableFakeWebSocketStream::SYNC, ERR_CONNECTION_CLOSED); set_stream(stream.Pass()); @@ -954,13 +893,12 @@ TEST_F(WebSocketChannelEventInterfaceTest, ConnectionCloseAfterHandshake) { TEST_F(WebSocketChannelEventInterfaceTest, NormalAsyncRead) { scoped_ptr<ReadableFakeWebSocketStream> stream( new ReadableFakeWebSocketStream); - static const InitFrameChunk chunks[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, 5}, - FINAL_CHUNK, "HELLO"}}; + static const InitFrame frames[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, "HELLO"}}; // We use this checkpoint object to verify that the callback isn't called // until we expect it to be. MockFunction<void(int)> checkpoint; - stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks); + stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, frames); set_stream(stream.Pass()); { InSequence s; @@ -985,14 +923,12 @@ TEST_F(WebSocketChannelEventInterfaceTest, NormalAsyncRead) { TEST_F(WebSocketChannelEventInterfaceTest, AsyncThenSyncRead) { scoped_ptr<ReadableFakeWebSocketStream> stream( new ReadableFakeWebSocketStream); - static const InitFrameChunk chunks1[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, 5}, - FINAL_CHUNK, "HELLO"}}; - static const InitFrameChunk chunks2[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, 5}, - FINAL_CHUNK, "WORLD"}}; - stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks1); - stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, chunks2); + static const InitFrame frames1[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, "HELLO"}}; + static const InitFrame frames2[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, "WORLD"}}; + stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, frames1); + stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, frames2); set_stream(stream.Pass()); { InSequence s; @@ -1012,31 +948,29 @@ TEST_F(WebSocketChannelEventInterfaceTest, AsyncThenSyncRead) { base::MessageLoop::current()->RunUntilIdle(); } -// Data frames that arrive in fragments are turned into individual frames -TEST_F(WebSocketChannelEventInterfaceTest, FragmentedFrames) { +// Data frames are delivered the same regardless of how many reads they arrive +// as. +TEST_F(WebSocketChannelEventInterfaceTest, FragmentedMessage) { scoped_ptr<ReadableFakeWebSocketStream> stream( new ReadableFakeWebSocketStream); - // Here we have one message split into 3 frames which arrive in 3 chunks. The - // first frame is entirely in the first chunk, the second frame is split - // across all the chunks, and the final frame is entirely in the final - // chunk. The frame fragments are converted to separate frames so that they - // can be delivered immediatedly. So the EventInterface should see a Text - // message with 5 frames. - static const InitFrameChunk chunks1[] = { - {{NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, 5}, - FINAL_CHUNK, "THREE"}, - {{NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeContinuation, NOT_MASKED, - 7}, - NOT_FINAL_CHUNK, " "}}; - static const InitFrameChunk chunks2[] = { - {{NO_HEADER}, NOT_FINAL_CHUNK, "SMALL"}}; - static const InitFrameChunk chunks3[] = { - {{NO_HEADER}, FINAL_CHUNK, " "}, - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeContinuation, NOT_MASKED, 6}, - FINAL_CHUNK, "FRAMES"}}; - stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks1); - stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks2); - stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks3); + // Here we have one message which arrived in five frames split across three + // reads. It may have been reframed on arrival, but this class doesn't care + // about that. + static const InitFrame frames1[] = { + {NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, "THREE"}, + {NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeContinuation, + NOT_MASKED, " "}}; + static const InitFrame frames2[] = { + {NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeContinuation, + NOT_MASKED, "SMALL"}}; + static const InitFrame frames3[] = { + {NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeContinuation, + NOT_MASKED, " "}, + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeContinuation, + NOT_MASKED, "FRAMES"}}; + stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, frames1); + stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, frames2); + stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, frames3); set_stream(stream.Pass()); { InSequence s; @@ -1068,112 +1002,16 @@ TEST_F(WebSocketChannelEventInterfaceTest, FragmentedFrames) { base::MessageLoop::current()->RunUntilIdle(); } -// In the case when a single-frame message because fragmented, it must be -// correctly transformed to multiple frames. -TEST_F(WebSocketChannelEventInterfaceTest, MessageFragmentation) { - scoped_ptr<ReadableFakeWebSocketStream> stream( - new ReadableFakeWebSocketStream); - // A single-frame Text message arrives in three chunks. This should be - // delivered as three frames. - static const InitFrameChunk chunks1[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, 12}, - NOT_FINAL_CHUNK, "TIME"}}; - static const InitFrameChunk chunks2[] = { - {{NO_HEADER}, NOT_FINAL_CHUNK, " FOR "}}; - static const InitFrameChunk chunks3[] = {{{NO_HEADER}, FINAL_CHUNK, "TEA"}}; - stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks1); - stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks2); - stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks3); - set_stream(stream.Pass()); - { - InSequence s; - EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _)); - EXPECT_CALL(*event_interface_, OnFlowControl(_)); - EXPECT_CALL( - *event_interface_, - OnDataFrame( - false, WebSocketFrameHeader::kOpCodeText, AsVector("TIME"))); - EXPECT_CALL(*event_interface_, - OnDataFrame(false, - WebSocketFrameHeader::kOpCodeContinuation, - AsVector(" FOR "))); - EXPECT_CALL( - *event_interface_, - OnDataFrame( - true, WebSocketFrameHeader::kOpCodeContinuation, AsVector("TEA"))); - } - - CreateChannelAndConnectSuccessfully(); - base::MessageLoop::current()->RunUntilIdle(); -} - -// If a control message is fragmented, it must be re-assembled before being -// delivered. A control message can only be fragmented at the network level; it -// is not permitted to be split into multiple frames. -TEST_F(WebSocketChannelEventInterfaceTest, FragmentedControlMessage) { - scoped_ptr<ReadableFakeWebSocketStream> stream( - new ReadableFakeWebSocketStream); - static const InitFrameChunk chunks1[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, 7}, - NOT_FINAL_CHUNK, CLOSE_DATA(NORMAL_CLOSURE, "")}}; - static const InitFrameChunk chunks2[] = { - {{NO_HEADER}, NOT_FINAL_CHUNK, "Clo"}}; - static const InitFrameChunk chunks3[] = {{{NO_HEADER}, FINAL_CHUNK, "se"}}; - stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks1); - stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks2); - stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks3); - stream->PrepareReadFramesError(ReadableFakeWebSocketStream::ASYNC, - ERR_CONNECTION_CLOSED); - set_stream(stream.Pass()); - { - InSequence s; - EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _)); - EXPECT_CALL(*event_interface_, OnFlowControl(_)); - EXPECT_CALL(*event_interface_, OnClosingHandshake()); - EXPECT_CALL(*event_interface_, - OnDropChannel(kWebSocketNormalClosure, "Close")); - } - - CreateChannelAndConnectSuccessfully(); - base::MessageLoop::current()->RunUntilIdle(); -} - -// The payload of a control frame is not permitted to exceed 125 bytes. RFC6455 -// 5.5 "All control frames MUST have a payload length of 125 bytes or less" -TEST_F(WebSocketChannelEventInterfaceTest, OversizeControlMessageIsRejected) { - scoped_ptr<ReadableFakeWebSocketStream> stream( - new ReadableFakeWebSocketStream); - static const size_t kPayloadLen = 126; - char payload[kPayloadLen + 1]; // allow space for trailing NUL - std::fill(payload, payload + kPayloadLen, 'A'); - payload[kPayloadLen] = '\0'; - // Not static because "payload" is constructed at runtime. - const InitFrameChunk chunks[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodePing, NOT_MASKED, - kPayloadLen}, - FINAL_CHUNK, payload}}; - stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, chunks); - set_stream(stream.Pass()); - - EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _)); - EXPECT_CALL(*event_interface_, OnFlowControl(_)); - EXPECT_CALL(*event_interface_, - OnDropChannel(kWebSocketErrorProtocolError, _)); - - CreateChannelAndConnectSuccessfully(); -} - // A control frame is not permitted to be split into multiple frames. RFC6455 // 5.5 "All control frames ... MUST NOT be fragmented." TEST_F(WebSocketChannelEventInterfaceTest, MultiFrameControlMessageIsRejected) { scoped_ptr<ReadableFakeWebSocketStream> stream( new ReadableFakeWebSocketStream); - static const InitFrameChunk chunks[] = { - {{NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodePing, NOT_MASKED, 2}, - FINAL_CHUNK, "Pi"}, - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeContinuation, NOT_MASKED, 2}, - FINAL_CHUNK, "ng"}}; - stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks); + static const InitFrame frames[] = { + {NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodePing, NOT_MASKED, "Pi"}, + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeContinuation, + NOT_MASKED, "ng"}}; + stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, frames); set_stream(stream.Pass()); { InSequence s; @@ -1225,39 +1063,14 @@ TEST_F(WebSocketChannelEventInterfaceTest, ConnectionReset) { base::MessageLoop::current()->RunUntilIdle(); } -// Connection closed in the middle of a Close message (server bug, etc.) -TEST_F(WebSocketChannelEventInterfaceTest, ConnectionClosedInMessage) { - scoped_ptr<ReadableFakeWebSocketStream> stream( - new ReadableFakeWebSocketStream); - static const InitFrameChunk chunks[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, 7}, - NOT_FINAL_CHUNK, CLOSE_DATA(NORMAL_CLOSURE, "")}}; - - stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks); - stream->PrepareReadFramesError(ReadableFakeWebSocketStream::ASYNC, - ERR_CONNECTION_CLOSED); - set_stream(stream.Pass()); - { - InSequence s; - EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _)); - EXPECT_CALL(*event_interface_, OnFlowControl(_)); - EXPECT_CALL(*event_interface_, - OnDropChannel(kWebSocketErrorAbnormalClosure, _)); - } - - CreateChannelAndConnectSuccessfully(); - base::MessageLoop::current()->RunUntilIdle(); -} - // RFC6455 5.1 "A client MUST close a connection if it detects a masked frame." TEST_F(WebSocketChannelEventInterfaceTest, MaskedFramesAreRejected) { scoped_ptr<ReadableFakeWebSocketStream> stream( new ReadableFakeWebSocketStream); - static const InitFrameChunk chunks[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, 5}, FINAL_CHUNK, - "HELLO"}}; + static const InitFrame frames[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, "HELLO"}}; - stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks); + stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, frames); set_stream(stream.Pass()); { InSequence s; @@ -1276,10 +1089,9 @@ TEST_F(WebSocketChannelEventInterfaceTest, MaskedFramesAreRejected) { TEST_F(WebSocketChannelEventInterfaceTest, UnknownOpCodeIsRejected) { scoped_ptr<ReadableFakeWebSocketStream> stream( new ReadableFakeWebSocketStream); - static const InitFrameChunk chunks[] = { - {{FINAL_FRAME, 4, NOT_MASKED, 5}, FINAL_CHUNK, "HELLO"}}; + static const InitFrame frames[] = {{FINAL_FRAME, 4, NOT_MASKED, "HELLO"}}; - stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks); + stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, frames); set_stream(stream.Pass()); { InSequence s; @@ -1300,18 +1112,17 @@ TEST_F(WebSocketChannelEventInterfaceTest, ControlFrameInDataMessage) { new ReadableFakeWebSocketStream); // We have one message of type Text split into two frames. In the middle is a // control message of type Pong. - static const InitFrameChunk chunks1[] = { - {{NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, 6}, - FINAL_CHUNK, "SPLIT "}}; - static const InitFrameChunk chunks2[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodePong, NOT_MASKED, 0}, - FINAL_CHUNK, ""}}; - static const InitFrameChunk chunks3[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeContinuation, NOT_MASKED, 7}, - FINAL_CHUNK, "MESSAGE"}}; - stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks1); - stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks2); - stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks3); + static const InitFrame frames1[] = { + {NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, + NOT_MASKED, "SPLIT "}}; + static const InitFrame frames2[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodePong, NOT_MASKED, ""}}; + static const InitFrame frames3[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeContinuation, + NOT_MASKED, "MESSAGE"}}; + stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, frames1); + stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, frames2); + stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, frames3); set_stream(stream.Pass()); { InSequence s; @@ -1331,17 +1142,16 @@ TEST_F(WebSocketChannelEventInterfaceTest, ControlFrameInDataMessage) { base::MessageLoop::current()->RunUntilIdle(); } -// If a chunk has an invalid header, then the connection is closed and -// subsequent chunks must not trigger events. -TEST_F(WebSocketChannelEventInterfaceTest, HeaderlessChunkAfterInvalidChunk) { +// If a frame has an invalid header, then the connection is closed and +// subsequent frames must not trigger events. +TEST_F(WebSocketChannelEventInterfaceTest, FrameAfterInvalidFrame) { scoped_ptr<ReadableFakeWebSocketStream> stream( new ReadableFakeWebSocketStream); - static const InitFrameChunk chunks[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, 11}, - NOT_FINAL_CHUNK, "HELLO"}, - {{NO_HEADER}, FINAL_CHUNK, " WORLD"}}; + static const InitFrame frames[] = { + {NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, "HELLO"}, + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, " WORLD"}}; - stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks); + stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, frames); set_stream(stream.Pass()); { InSequence s; @@ -1501,10 +1311,9 @@ TEST_F(WebSocketChannelEventInterfaceTest, OnDropChannelCalledOnce) { TEST_F(WebSocketChannelEventInterfaceTest, CloseWithNoPayloadGivesStatus1005) { scoped_ptr<ReadableFakeWebSocketStream> stream( new ReadableFakeWebSocketStream); - static const InitFrameChunk chunks[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, 0}, - FINAL_CHUNK, ""}}; - stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, chunks); + static const InitFrame frames[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, ""}}; + stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, frames); stream->PrepareReadFramesError(ReadableFakeWebSocketStream::SYNC, ERR_CONNECTION_CLOSED); set_stream(stream.Pass()); @@ -1517,16 +1326,50 @@ TEST_F(WebSocketChannelEventInterfaceTest, CloseWithNoPayloadGivesStatus1005) { CreateChannelAndConnectSuccessfully(); } +// If ReadFrames() returns ERR_WS_PROTOCOL_ERROR, then +// kWebSocketErrorProtocolError must be sent to the renderer. +TEST_F(WebSocketChannelEventInterfaceTest, SyncProtocolErrorGivesStatus1002) { + scoped_ptr<ReadableFakeWebSocketStream> stream( + new ReadableFakeWebSocketStream); + stream->PrepareReadFramesError(ReadableFakeWebSocketStream::SYNC, + ERR_WS_PROTOCOL_ERROR); + set_stream(stream.Pass()); + EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _)); + EXPECT_CALL(*event_interface_, OnFlowControl(_)); + + EXPECT_CALL(*event_interface_, + OnDropChannel(kWebSocketErrorProtocolError, _)); + + CreateChannelAndConnectSuccessfully(); +} + +// Async version of above test. +TEST_F(WebSocketChannelEventInterfaceTest, AsyncProtocolErrorGivesStatus1002) { + scoped_ptr<ReadableFakeWebSocketStream> stream( + new ReadableFakeWebSocketStream); + stream->PrepareReadFramesError(ReadableFakeWebSocketStream::ASYNC, + ERR_WS_PROTOCOL_ERROR); + set_stream(stream.Pass()); + EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _)); + EXPECT_CALL(*event_interface_, OnFlowControl(_)); + + EXPECT_CALL(*event_interface_, + OnDropChannel(kWebSocketErrorProtocolError, _)); + + CreateChannelAndConnectSuccessfully(); + base::MessageLoop::current()->RunUntilIdle(); +} + // RFC6455 5.1 "a client MUST mask all frames that it sends to the server". // WebSocketChannel actually only sets the mask bit in the header, it doesn't // perform masking itself (not all transports actually use masking). TEST_F(WebSocketChannelStreamTest, SentFramesAreMasked) { - static const InitFrameChunk expected[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, 13}, - FINAL_CHUNK, "NEEDS MASKING"}}; + static const InitFrame expected[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, + MASKED, "NEEDS MASKING"}}; EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber()); EXPECT_CALL(*mock_stream_, ReadFrames(_, _)).WillOnce(Return(ERR_IO_PENDING)); - EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected), _)) + EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected), _)) .WillOnce(Return(OK)); CreateChannelAndConnectSuccessfully(); @@ -1537,12 +1380,12 @@ TEST_F(WebSocketChannelStreamTest, SentFramesAreMasked) { // RFC6455 5.5.1 "The application MUST NOT send any more data frames after // sending a Close frame." TEST_F(WebSocketChannelStreamTest, NothingIsSentAfterClose) { - static const InitFrameChunk expected[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, MASKED, 9}, - FINAL_CHUNK, CLOSE_DATA(NORMAL_CLOSURE, "Success")}}; + static const InitFrame expected[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, + MASKED, CLOSE_DATA(NORMAL_CLOSURE, "Success")}}; EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber()); EXPECT_CALL(*mock_stream_, ReadFrames(_, _)).WillOnce(Return(ERR_IO_PENDING)); - EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected), _)) + EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected), _)) .WillOnce(Return(OK)); CreateChannelAndConnectSuccessfully(); @@ -1554,17 +1397,17 @@ TEST_F(WebSocketChannelStreamTest, NothingIsSentAfterClose) { // RFC6455 5.5.1 "If an endpoint receives a Close frame and did not previously // send a Close frame, the endpoint MUST send a Close frame in response." TEST_F(WebSocketChannelStreamTest, CloseIsEchoedBack) { - static const InitFrameChunk chunks[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, 7}, - FINAL_CHUNK, CLOSE_DATA(NORMAL_CLOSURE, "Close")}}; - static const InitFrameChunk expected[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, MASKED, 7}, - FINAL_CHUNK, CLOSE_DATA(NORMAL_CLOSURE, "Close")}}; + static const InitFrame frames[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, + NOT_MASKED, CLOSE_DATA(NORMAL_CLOSURE, "Close")}}; + static const InitFrame expected[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, + MASKED, CLOSE_DATA(NORMAL_CLOSURE, "Close")}}; EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber()); EXPECT_CALL(*mock_stream_, ReadFrames(_, _)) - .WillOnce(ReturnChunks(&chunks)) + .WillOnce(ReturnFrames(&frames)) .WillRepeatedly(Return(ERR_IO_PENDING)); - EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected), _)) + EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected), _)) .WillOnce(Return(OK)); CreateChannelAndConnectSuccessfully(); @@ -1573,17 +1416,17 @@ TEST_F(WebSocketChannelStreamTest, CloseIsEchoedBack) { // The converse of the above case; after sending a Close frame, we should not // send another one. TEST_F(WebSocketChannelStreamTest, CloseOnlySentOnce) { - static const InitFrameChunk expected[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, MASKED, 7}, - FINAL_CHUNK, CLOSE_DATA(NORMAL_CLOSURE, "Close")}}; - static const InitFrameChunk chunks[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, 7}, - FINAL_CHUNK, CLOSE_DATA(NORMAL_CLOSURE, "Close")}}; + static const InitFrame expected[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, + MASKED, CLOSE_DATA(NORMAL_CLOSURE, "Close")}}; + static const InitFrame frames_init[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, + NOT_MASKED, CLOSE_DATA(NORMAL_CLOSURE, "Close")}}; // We store the parameters that were passed to ReadFrames() so that we can // call them explicitly later. CompletionCallback read_callback; - ScopedVector<WebSocketFrameChunk>* frame_chunks = NULL; + ScopedVector<WebSocketFrame>* frames = NULL; // Use a checkpoint to make the ordering of events clearer. MockFunction<void(int)> checkpoint; @@ -1591,11 +1434,11 @@ TEST_F(WebSocketChannelStreamTest, CloseOnlySentOnce) { InSequence s; EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber()); EXPECT_CALL(*mock_stream_, ReadFrames(_, _)) - .WillOnce(DoAll(SaveArg<0>(&frame_chunks), + .WillOnce(DoAll(SaveArg<0>(&frames), SaveArg<1>(&read_callback), Return(ERR_IO_PENDING))); EXPECT_CALL(checkpoint, Call(1)); - EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected), _)) + EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected), _)) .WillOnce(Return(OK)); EXPECT_CALL(checkpoint, Call(2)); EXPECT_CALL(*mock_stream_, ReadFrames(_, _)) @@ -1610,7 +1453,7 @@ TEST_F(WebSocketChannelStreamTest, CloseOnlySentOnce) { channel_->StartClosingHandshake(kWebSocketNormalClosure, "Close"); checkpoint.Call(2); - *frame_chunks = CreateFrameChunkVector(chunks); + *frames = CreateFrameVector(frames_init); read_callback.Run(OK); checkpoint.Call(3); } @@ -1621,17 +1464,15 @@ TEST_F(WebSocketChannelStreamTest, CloseOnlySentOnce) { // CloseWithNoPayloadGivesStatus1005, above, for confirmation that code 1005 is // correctly generated internally. TEST_F(WebSocketChannelStreamTest, Code1005IsNotEchoed) { - static const InitFrameChunk chunks[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, 0}, - FINAL_CHUNK, ""}}; - static const InitFrameChunk expected[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, MASKED, 0}, - FINAL_CHUNK, ""}}; + static const InitFrame frames[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, ""}}; + static const InitFrame expected[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, MASKED, ""}}; EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber()); EXPECT_CALL(*mock_stream_, ReadFrames(_, _)) - .WillOnce(ReturnChunks(&chunks)) + .WillOnce(ReturnFrames(&frames)) .WillRepeatedly(Return(ERR_IO_PENDING)); - EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected), _)) + EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected), _)) .WillOnce(Return(OK)); CreateChannelAndConnectSuccessfully(); @@ -1643,58 +1484,57 @@ TEST_F(WebSocketChannelStreamTest, Code1005IsNotEchoed) { // "Application data" as found in the message body of the Ping frame being // replied to." TEST_F(WebSocketChannelStreamTest, PingRepliedWithPong) { - static const InitFrameChunk chunks[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodePing, NOT_MASKED, 16}, - FINAL_CHUNK, "Application data"}}; - static const InitFrameChunk expected[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodePong, MASKED, 16}, - FINAL_CHUNK, "Application data"}}; + static const InitFrame frames[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodePing, + NOT_MASKED, "Application data"}}; + static const InitFrame expected[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodePong, + MASKED, "Application data"}}; EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber()); EXPECT_CALL(*mock_stream_, ReadFrames(_, _)) - .WillOnce(ReturnChunks(&chunks)) + .WillOnce(ReturnFrames(&frames)) .WillRepeatedly(Return(ERR_IO_PENDING)); - EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected), _)) + EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected), _)) .WillOnce(Return(OK)); CreateChannelAndConnectSuccessfully(); } TEST_F(WebSocketChannelStreamTest, PongInTheMiddleOfDataMessage) { - static const InitFrameChunk chunks[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodePing, NOT_MASKED, 16}, - FINAL_CHUNK, "Application data"}}; - static const InitFrameChunk expected1[] = { - {{NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, 6}, - FINAL_CHUNK, "Hello "}}; - static const InitFrameChunk expected2[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodePong, MASKED, 16}, - FINAL_CHUNK, "Application data"}}; - static const InitFrameChunk expected3[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeContinuation, MASKED, 5}, - FINAL_CHUNK, "World"}}; - ScopedVector<WebSocketFrameChunk>* read_chunks; + static const InitFrame frames[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodePing, + NOT_MASKED, "Application data"}}; + static const InitFrame expected1[] = { + {NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, "Hello "}}; + static const InitFrame expected2[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodePong, + MASKED, "Application data"}}; + static const InitFrame expected3[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeContinuation, + MASKED, "World"}}; + ScopedVector<WebSocketFrame>* read_frames; CompletionCallback read_callback; EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber()); EXPECT_CALL(*mock_stream_, ReadFrames(_, _)) - .WillOnce(DoAll(SaveArg<0>(&read_chunks), + .WillOnce(DoAll(SaveArg<0>(&read_frames), SaveArg<1>(&read_callback), Return(ERR_IO_PENDING))) .WillRepeatedly(Return(ERR_IO_PENDING)); { InSequence s; - EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected1), _)) + EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected1), _)) .WillOnce(Return(OK)); - EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected2), _)) + EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected2), _)) .WillOnce(Return(OK)); - EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected3), _)) + EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected3), _)) .WillOnce(Return(OK)); } CreateChannelAndConnectSuccessfully(); channel_->SendFrame( false, WebSocketFrameHeader::kOpCodeText, AsVector("Hello ")); - *read_chunks = CreateFrameChunkVector(chunks); + *read_frames = CreateFrameVector(frames); read_callback.Run(OK); channel_->SendFrame( true, WebSocketFrameHeader::kOpCodeContinuation, AsVector("World")); @@ -1703,12 +1543,10 @@ TEST_F(WebSocketChannelStreamTest, PongInTheMiddleOfDataMessage) { // WriteFrames() may not be called until the previous write has completed. // WebSocketChannel must buffer writes that happen in the meantime. TEST_F(WebSocketChannelStreamTest, WriteFramesOneAtATime) { - static const InitFrameChunk expected1[] = { - {{NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, 6}, - FINAL_CHUNK, "Hello "}}; - static const InitFrameChunk expected2[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, 5}, FINAL_CHUNK, - "World"}}; + static const InitFrame expected1[] = { + {NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, "Hello "}}; + static const InitFrame expected2[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, "World"}}; CompletionCallback write_callback; MockFunction<void(int)> checkpoint; @@ -1717,10 +1555,10 @@ TEST_F(WebSocketChannelStreamTest, WriteFramesOneAtATime) { { InSequence s; EXPECT_CALL(checkpoint, Call(1)); - EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected1), _)) + EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected1), _)) .WillOnce(DoAll(SaveArg<1>(&write_callback), Return(ERR_IO_PENDING))); EXPECT_CALL(checkpoint, Call(2)); - EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected2), _)) + EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected2), _)) .WillOnce(Return(ERR_IO_PENDING)); EXPECT_CALL(checkpoint, Call(3)); } @@ -1741,27 +1579,22 @@ TEST_F(WebSocketChannelStreamTest, WriteFramesOneAtATime) { // important to get good throughput in the "many small messages" case. TEST_F(WebSocketChannelStreamTest, WaitingMessagesAreBatched) { static const char input_letters[] = "Hello"; - static const InitFrameChunk expected1[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, 1}, FINAL_CHUNK, - "H"}}; - static const InitFrameChunk expected2[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, 1}, FINAL_CHUNK, - "e"}, - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, 1}, FINAL_CHUNK, - "l"}, - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, 1}, FINAL_CHUNK, - "l"}, - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, 1}, FINAL_CHUNK, - "o"}}; + static const InitFrame expected1[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, "H"}}; + static const InitFrame expected2[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, "e"}, + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, "l"}, + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, "l"}, + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, "o"}}; CompletionCallback write_callback; EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber()); EXPECT_CALL(*mock_stream_, ReadFrames(_, _)).WillOnce(Return(ERR_IO_PENDING)); { InSequence s; - EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected1), _)) + EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected1), _)) .WillOnce(DoAll(SaveArg<1>(&write_callback), Return(ERR_IO_PENDING))); - EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected2), _)) + EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected2), _)) .WillOnce(Return(ERR_IO_PENDING)); } @@ -1781,12 +1614,12 @@ TEST_F(WebSocketChannelStreamTest, WaitingMessagesAreBatched) { // even be using a different extension which uses that code to mean something // else. TEST_F(WebSocketChannelStreamTest, MuxErrorIsNotSentToStream) { - static const InitFrameChunk expected[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, MASKED, 16}, - FINAL_CHUNK, CLOSE_DATA(GOING_AWAY, "Internal Error")}}; + static const InitFrame expected[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, + MASKED, CLOSE_DATA(GOING_AWAY, "Internal Error")}}; EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber()); EXPECT_CALL(*mock_stream_, ReadFrames(_, _)).WillOnce(Return(ERR_IO_PENDING)); - EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected), _)) + EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected), _)) .WillOnce(Return(OK)); EXPECT_CALL(*mock_stream_, Close()); @@ -1800,45 +1633,41 @@ TEST_F(WebSocketChannelStreamTest, MuxErrorIsNotSentToStream) { // protocol also has Binary frames and those need to be 8-bit clean. For the // sake of completeness, this test verifies that they are. TEST_F(WebSocketChannelStreamTest, WrittenBinaryFramesAre8BitClean) { - ScopedVector<WebSocketFrameChunk>* frame_chunks = NULL; + ScopedVector<WebSocketFrame>* frames = NULL; EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber()); EXPECT_CALL(*mock_stream_, ReadFrames(_, _)).WillOnce(Return(ERR_IO_PENDING)); EXPECT_CALL(*mock_stream_, WriteFrames(_, _)) - .WillOnce(DoAll(SaveArg<0>(&frame_chunks), Return(ERR_IO_PENDING))); + .WillOnce(DoAll(SaveArg<0>(&frames), Return(ERR_IO_PENDING))); CreateChannelAndConnectSuccessfully(); channel_->SendFrame( true, WebSocketFrameHeader::kOpCodeBinary, std::vector<char>(kBinaryBlob, kBinaryBlob + kBinaryBlobSize)); - ASSERT_TRUE(frame_chunks != NULL); - ASSERT_EQ(1U, frame_chunks->size()); - const WebSocketFrameChunk* out_chunk = (*frame_chunks)[0]; - ASSERT_TRUE(out_chunk->header); - EXPECT_EQ(kBinaryBlobSize, out_chunk->header->payload_length); - ASSERT_TRUE(out_chunk->data); - EXPECT_EQ(kBinaryBlobSize, static_cast<size_t>(out_chunk->data->size())); - EXPECT_EQ(0, memcmp(kBinaryBlob, out_chunk->data->data(), kBinaryBlobSize)); + ASSERT_TRUE(frames != NULL); + ASSERT_EQ(1U, frames->size()); + const WebSocketFrame* out_frame = (*frames)[0]; + EXPECT_EQ(kBinaryBlobSize, out_frame->header.payload_length); + ASSERT_TRUE(out_frame->data); + EXPECT_EQ(0, memcmp(kBinaryBlob, out_frame->data->data(), kBinaryBlobSize)); } // Test the read path for 8-bit cleanliness as well. TEST_F(WebSocketChannelEventInterfaceTest, ReadBinaryFramesAre8BitClean) { - scoped_ptr<WebSocketFrameHeader> frame_header( - new WebSocketFrameHeader(WebSocketFrameHeader::kOpCodeBinary)); - frame_header->final = true; - frame_header->payload_length = kBinaryBlobSize; - scoped_ptr<WebSocketFrameChunk> frame_chunk(new WebSocketFrameChunk); - frame_chunk->header = frame_header.Pass(); - frame_chunk->final_chunk = true; - frame_chunk->data = new IOBufferWithSize(kBinaryBlobSize); - memcpy(frame_chunk->data->data(), kBinaryBlob, kBinaryBlobSize); - ScopedVector<WebSocketFrameChunk> chunks; - chunks.push_back(frame_chunk.release()); + scoped_ptr<WebSocketFrame> frame( + new WebSocketFrame(WebSocketFrameHeader::kOpCodeBinary)); + WebSocketFrameHeader& frame_header = frame->header; + frame_header.final = true; + frame_header.payload_length = kBinaryBlobSize; + frame->data = new IOBuffer(kBinaryBlobSize); + memcpy(frame->data->data(), kBinaryBlob, kBinaryBlobSize); + ScopedVector<WebSocketFrame> frames; + frames.push_back(frame.release()); scoped_ptr<ReadableFakeWebSocketStream> stream( new ReadableFakeWebSocketStream); stream->PrepareRawReadFrames( - ReadableFakeWebSocketStream::SYNC, OK, chunks.Pass()); + ReadableFakeWebSocketStream::SYNC, OK, frames.Pass()); set_stream(stream.Pass()); EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _)); EXPECT_CALL(*event_interface_, OnFlowControl(_)); @@ -1856,17 +1685,17 @@ TEST_F(WebSocketChannelEventInterfaceTest, ReadBinaryFramesAre8BitClean) { // but the current implementation fails the connection. Since a Close has // already been sent, this just means closing the connection. TEST_F(WebSocketChannelStreamTest, PingAfterCloseIsRejected) { - static const InitFrameChunk chunks[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, 4}, - FINAL_CHUNK, CLOSE_DATA(NORMAL_CLOSURE, "OK")}, - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodePing, NOT_MASKED, 9}, - FINAL_CHUNK, "Ping body"}}; - static const InitFrameChunk expected[] = { - {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, MASKED, 4}, - FINAL_CHUNK, CLOSE_DATA(NORMAL_CLOSURE, "OK")}}; + static const InitFrame frames[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, + NOT_MASKED, CLOSE_DATA(NORMAL_CLOSURE, "OK")}, + {FINAL_FRAME, WebSocketFrameHeader::kOpCodePing, + NOT_MASKED, "Ping body"}}; + static const InitFrame expected[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, + MASKED, CLOSE_DATA(NORMAL_CLOSURE, "OK")}}; EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber()); EXPECT_CALL(*mock_stream_, ReadFrames(_, _)) - .WillOnce(ReturnChunks(&chunks)) + .WillOnce(ReturnFrames(&frames)) .WillRepeatedly(Return(ERR_IO_PENDING)); { // We only need to verify the relative order of WriteFrames() and @@ -1874,7 +1703,7 @@ TEST_F(WebSocketChannelStreamTest, PingAfterCloseIsRejected) { // frame before calling ReadFrames() again, but that is an implementation // detail and better not to consider required behaviour. InSequence s; - EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected), _)) + EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected), _)) .WillOnce(Return(OK)); EXPECT_CALL(*mock_stream_, Close()).Times(1); } @@ -1882,5 +1711,21 @@ TEST_F(WebSocketChannelStreamTest, PingAfterCloseIsRejected) { CreateChannelAndConnectSuccessfully(); } +// A protocol error from the remote server should result in a close frame with +// status 1002, followed by the connection closing. +TEST_F(WebSocketChannelStreamTest, ProtocolError) { + static const InitFrame expected[] = { + {FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, + MASKED, CLOSE_DATA(PROTOCOL_ERROR, "WebSocket Protocol Error")}}; + EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber()); + EXPECT_CALL(*mock_stream_, ReadFrames(_, _)) + .WillOnce(Return(ERR_WS_PROTOCOL_ERROR)); + EXPECT_CALL(*mock_stream_, WriteFrames(EqualsFrames(expected), _)) + .WillOnce(Return(OK)); + EXPECT_CALL(*mock_stream_, Close()); + + CreateChannelAndConnectSuccessfully(); +} + } // namespace } // namespace net diff --git a/net/websockets/websocket_frame.cc b/net/websockets/websocket_frame.cc index f05b5b7..763712a 100644 --- a/net/websockets/websocket_frame.cc +++ b/net/websockets/websocket_frame.cc @@ -41,18 +41,27 @@ inline void MaskWebSocketFramePayloadByBytes( namespace net { -scoped_ptr<WebSocketFrameHeader> WebSocketFrameHeader::Clone() { +scoped_ptr<WebSocketFrameHeader> WebSocketFrameHeader::Clone() const { scoped_ptr<WebSocketFrameHeader> ret(new WebSocketFrameHeader(opcode)); - ret->final = final; - ret->reserved1 = reserved1; - ret->reserved2 = reserved2; - ret->reserved3 = reserved3; - ret->opcode = opcode; - ret->masked = masked; - ret->payload_length = payload_length; + ret->CopyFrom(*this); return ret.Pass(); } +void WebSocketFrameHeader::CopyFrom(const WebSocketFrameHeader& source) { + final = source.final; + reserved1 = source.reserved1; + reserved2 = source.reserved2; + reserved3 = source.reserved3; + opcode = source.opcode; + masked = source.masked; + payload_length = source.payload_length; +} + +WebSocketFrame::WebSocketFrame(WebSocketFrameHeader::OpCode opcode) + : header(opcode) {} + +WebSocketFrame::~WebSocketFrame() {} + WebSocketFrameChunk::WebSocketFrameChunk() : final_chunk(false) {} WebSocketFrameChunk::~WebSocketFrameChunk() {} @@ -195,7 +204,7 @@ void MaskWebSocketFramePayload(const WebSocketMaskingKey& masking_key, // Create a version of the mask which is rotated by the appropriate offset // for our alignment. The "trick" here is that 0 XORed with the mask will // give the value of the mask for the appropriate byte. - char realigned_mask[kMaskingKeyLength] = { 0 }; + char realigned_mask[kMaskingKeyLength] = {}; MaskWebSocketFramePayloadByBytes( masking_key, (frame_offset + aligned_begin - data) % kMaskingKeyLength, diff --git a/net/websockets/websocket_frame.h b/net/websockets/websocket_frame.h index 8c04f7d..c9d8b97 100644 --- a/net/websockets/websocket_frame.h +++ b/net/websockets/websocket_frame.h @@ -14,6 +14,7 @@ namespace net { +class IOBuffer; class IOBufferWithSize; // Represents a WebSocket frame header. @@ -69,7 +70,10 @@ struct NET_EXPORT WebSocketFrameHeader { payload_length(0) {} // Create a clone of this object on the heap. - scoped_ptr<WebSocketFrameHeader> Clone(); + scoped_ptr<WebSocketFrameHeader> Clone() const; + + // Overwrite this object with the fields from |source|. + void CopyFrom(const WebSocketFrameHeader& source); // Members below correspond to each item in WebSocket frame header. // See <http://tools.ietf.org/html/rfc6455#section-5.2> for details. @@ -85,16 +89,32 @@ struct NET_EXPORT WebSocketFrameHeader { DISALLOW_COPY_AND_ASSIGN(WebSocketFrameHeader); }; -// Contains payload data of part of a WebSocket frame. +// Contains an entire WebSocket frame including payload. This is used by APIs +// that are not concerned about retaining the original frame boundaries (because +// frames may need to be split in order for the data to fit in memory). +struct NET_EXPORT_PRIVATE WebSocketFrame { + // A frame must always have an opcode, so this parameter is compulsory. + explicit WebSocketFrame(WebSocketFrameHeader::OpCode opcode); + ~WebSocketFrame(); + + // |header| is always present. + WebSocketFrameHeader header; + + // |data| is always unmasked even if the frame is masked. The size of |data| + // is given by |header.payload_length|. + scoped_refptr<IOBuffer> data; +}; + +// Structure describing one chunk of a WebSocket frame. // -// Payload of a WebSocket frame may be divided into multiple chunks. +// The 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. +// Frame dissection is necessary to handle frames that are too large to store in +// the browser memory without losing information about the frame boundaries. In +// practice, most code does not need to worry about the original frame +// boundaries and can use the WebSocketFrame type declared above. // // 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. diff --git a/net/websockets/websocket_stream.h b/net/websockets/websocket_stream.h index 4885bbe..ca29f57 100644 --- a/net/websockets/websocket_stream.h +++ b/net/websockets/websocket_stream.h @@ -24,7 +24,7 @@ class BoundNetLog; class HttpRequestHeaders; class HttpResponseInfo; class URLRequestContext; -struct WebSocketFrameChunk; +struct WebSocketFrame; // WebSocketStreamRequest is the caller's handle to the process of creation of a // WebSocketStream. Deleting the object before the OnSuccess or OnFailure @@ -91,10 +91,10 @@ class NET_EXPORT_PRIVATE WebSocketStream : public WebSocketStreamBase { virtual ~WebSocketStream(); // Reads WebSocket frame data. This operation finishes when new frame data - // becomes available. Each frame message might be chopped off in the middle - // as specified in the description of the WebSocketFrameChunk struct. - // |frame_chunks| remains owned by the caller and must be valid until the - // operation completes or Close() is called. |frame_chunks| must be empty on + // becomes available. + // + // |frames| remains owned by the caller and must be valid until the + // operation completes or Close() is called. |frames| must be empty on // calling. // // This function should not be called while the previous call of ReadFrames() @@ -102,20 +102,18 @@ class NET_EXPORT_PRIVATE WebSocketStream : public WebSocketStreamBase { // // Returns net::OK or one of the net::ERR_* codes. // - // frame_chunks->size() >= 1 if the result is OK. + // frames->size() >= 1 if the result is OK. // - // A frame with an incomplete header will never be inserted into - // |frame_chunks|. If the currently available bytes of a new frame do not form - // a complete frame header, then the implementation will buffer them until all - // the fields in the WebSocketFrameHeader object can be filled. If - // ReadFrames() is freshly called in this situation, it will return - // ERR_IO_PENDING exactly as if no data was available. + // Only frames with complete header information are inserted into |frames|. If + // the currently available bytes of a new frame do not form a complete frame + // header, then the implementation will buffer them until all the fields in + // the WebSocketFrameHeader object can be filled. If ReadFrames() is freshly + // called in this situation, it will return ERR_IO_PENDING exactly as if no + // data was available. // - // Every WebSocketFrameChunk in the vector except the first and last is - // guaranteed to be a complete frame. The first chunk may be the final part - // of the previous frame. The last chunk may be the first part of a new - // frame. If there is only one chunk, then it may consist of data from the - // middle part of a frame. + // Original frame boundaries are not preserved. In particular, if only part of + // a frame is available, then the frame will be split, and the available data + // will be returned immediately. // // When the socket is closed on the remote side, this method will return // ERR_CONNECTION_CLOSED. It will not return OK with an empty vector. @@ -124,33 +122,23 @@ class NET_EXPORT_PRIVATE WebSocketStream : public WebSocketStreamBase { // ReadFrames may discard the incomplete frame. Since the renderer will // discard any incomplete messages when the connection is closed, this makes // no difference to the overall semantics. - virtual int ReadFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks, + virtual int ReadFrames(ScopedVector<WebSocketFrame>* frames, const CompletionCallback& callback) = 0; - // Writes WebSocket frame data. |frame_chunks| must only contain complete - // frames. Every chunk must have a non-NULL |header| and the |final_chunk| - // boolean set to true. - // - // The |frame_chunks| pointer must remain valid until the operation completes - // or Close() is called. WriteFrames() will modify the contents of - // |frame_chunks| in the process of sending the message. After WriteFrames() - // has completed it is safe to clear and then re-use the vector, but other - // than that the caller should make no assumptions about its contents. + // Writes WebSocket frame data. // - // This function should not be called while a previous call to WriteFrames() - // on the same stream is pending. + // |frames| must be valid until the operation completes or Close() is called. // - // Frame boundaries may not be preserved. Frames may be split or - // coalesced. Message boundaries are preserved (as required by WebSocket API - // semantics). + // This function must not be called while a previous call of WriteFrames() is + // still pending. // // This method will only return OK if all frames were written completely. // Otherwise it will return an appropriate net error code. - virtual int WriteFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks, + virtual int WriteFrames(ScopedVector<WebSocketFrame>* frames, const CompletionCallback& callback) = 0; // Closes the stream. All pending I/O operations (if any) are cancelled - // at this point, so |frame_chunks| can be freed. + // at this point, so |frames| can be freed. virtual void Close() = 0; // The subprotocol that was negotiated for the stream. If no protocol was |