Chromium Code Reviews| Index: net/websockets/websocket_frame_builder.cc |
| diff --git a/net/websockets/websocket_frame_builder.cc b/net/websockets/websocket_frame_builder.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..b5d6684de66ca6ff53f7e3792314adecb39bdda0 |
| --- /dev/null |
| +++ b/net/websockets/websocket_frame_builder.cc |
| @@ -0,0 +1,212 @@ |
| +// Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "net/websockets/websocket_frame_builder.h" |
| + |
| +#include <algorithm> |
| + |
| +#include "base/basictypes.h" |
| +#include "base/logging.h" |
| +#include "base/memory/scoped_ptr.h" |
| +#include "base/memory/scoped_vector.h" |
| +#include "base/rand_util.h" |
| +#include "net/base/big_endian.h" |
| +#include "net/websockets/websocket_frame.h" |
| + |
| +namespace { |
| + |
| +const uint8 kFinalBit = 0x80; |
| +const uint8 kReserved1Bit = 0x40; |
| +const uint8 kReserved2Bit = 0x20; |
| +const uint8 kReserved3Bit = 0x10; |
| +const uint8 kOpCodeMask = 0xF; |
| +const uint8 kMaskBit = 0x80; |
| +const uint64 kMaxPayloadLengthWithoutExtendedLengthField = 125; |
| +const uint64 kPayloadLengthWithTwoByteExtendedLengthField = 126; |
| +const uint64 kPayloadLengthWithEightByteExtendedLengthField = 127; |
| + |
| +} // Unnamed namespace. |
| + |
| +namespace net { |
| + |
| +WebSocketFrameBuilder::WebSocketFrameBuilder() |
| + : frame_offset_(0), |
| + has_pinned_masking_key_for_testing_(false), |
| + failed_(false) { |
| + std::fill(masking_key_, |
| + masking_key_ + WebSocketFrameHeader::kMaskingKeyLength, |
| + '\0'); |
| + std::fill( |
| + pinned_masking_key_for_testing_, |
| + pinned_masking_key_for_testing_ + WebSocketFrameHeader::kMaskingKeyLength, |
| + '\0'); |
| +} |
| + |
| +WebSocketFrameBuilder::~WebSocketFrameBuilder() { |
| +} |
| + |
| +bool WebSocketFrameBuilder::Encode( |
| + ScopedVector<WebSocketFrameChunk> frame_chunks, |
| + std::vector<char>* output) { |
| + if (failed_) |
| + return false; |
| + |
| + // We DCHECK inconsistency of |frame_chunks| such as payload length mismatch |
|
mmenke
2012/05/15 18:36:31
nit: Think "We DCHECK on inconsistent |frame_chun
|
| + // or missing |header| in the first chunk, because these are programming |
| + // errors we want to avoid. When DCHECK isn't functional (i.e. Release and |
| + // !DCHECK_ALWAYS_ON), we fail gracefully on these errors. |
| + for (ScopedVector<WebSocketFrameChunk>::iterator iter = frame_chunks.begin(); |
| + iter != frame_chunks.end(); ++iter) { |
| + WebSocketFrameChunk* chunk = *iter; |
| + if (chunk->header.get()) { // Beginning of a new frame. |
| + DCHECK(!current_frame_header_.get()); |
| + DCHECK_EQ(0u, frame_offset_); |
| + if (current_frame_header_.get() || frame_offset_ != 0u) { |
| + Fail(); |
| + return false; |
| + } |
| + current_frame_header_ = chunk->header.Pass(); |
| + if (current_frame_header_->masked) |
| + GenerateNewMaskingKey(); |
| + if (!EncodeFrameHeader(current_frame_header_.get(), output)) { |
| + Fail(); |
| + return false; |
| + } |
| + } |
| + |
| + DCHECK(current_frame_header_.get()); |
| + if (!current_frame_header_.get()) { |
| + Fail(); |
| + return false; |
| + } |
| + |
| + if (current_frame_header_->masked) |
|
mmenke
2012/05/15 18:36:31
According to specs, it's invalid for this to be fa
|
| + MaskPayload(&chunk->data); |
| + // FIXME(yutak): Remove copy here. Ultimately, we probably need to have |
| + // "gather writes" ability (aka vectored I/O) in the Socket interface |
| + // so we can send data from multiple local buffers without data copies. |
| + output->insert(output->end(), chunk->data.begin(), chunk->data.end()); |
| + frame_offset_ += chunk->data.size(); |
| + DCHECK_LE(frame_offset_, current_frame_header_->payload_length); |
| + if (frame_offset_ > current_frame_header_->payload_length) { |
| + Fail(); |
| + return false; |
| + } |
| + |
| + if (chunk->final_chunk) { |
| + // Throw away the information about the current frame to get ready |
| + // for the next frame. |
| + DCHECK_EQ(frame_offset_, current_frame_header_->payload_length); |
| + if (frame_offset_ != current_frame_header_->payload_length) { |
|
mmenke
2012/05/15 18:36:31
I'm a bit worried about the interface complexity a
|
| + Fail(); |
| + return false; |
| + } |
| + current_frame_header_.reset(); |
| + frame_offset_ = 0; |
| + } |
| + } |
| + |
| + return true; |
| +} |
| + |
| +bool WebSocketFrameBuilder::EncodeFrameHeader( |
| + WebSocketFrameHeader* header, |
| + std::vector<char>* output) const { |
| + DCHECK(header); |
| + |
| + // header->opcode must fit to kOpCodeMask. |
| + DCHECK((header->opcode & kOpCodeMask) == header->opcode); |
| + |
| + uint8 first_byte = 0u; |
| + first_byte |= header->final ? kFinalBit : 0u; |
| + first_byte |= header->reserved1 ? kReserved1Bit : 0u; |
| + first_byte |= header->reserved2 ? kReserved2Bit : 0u; |
| + first_byte |= header->reserved3 ? kReserved3Bit : 0u; |
| + first_byte |= header->opcode; |
| + |
| + uint8 second_byte = 0; |
|
mmenke
2012/05/15 18:36:31
nit: 0u
|
| + second_byte |= header->masked ? kMaskBit : 0u; |
| + if (header->payload_length <= |
| + kMaxPayloadLengthWithoutExtendedLengthField) { |
| + second_byte |= header->payload_length; |
| + } else if (header->payload_length <= kuint16max) { |
| + second_byte |= kPayloadLengthWithTwoByteExtendedLengthField; |
| + } else if (header->payload_length <= static_cast<uint64>(kint64max)) { |
| + second_byte |= kPayloadLengthWithEightByteExtendedLengthField; |
| + } else { |
| + // WebSocket protocol specification doesn't allow payload length to |
| + // exceed kint64max (0x7FFFFFFFFFFFFFFF). |
| + return false; |
| + } |
| + |
| + output->push_back(first_byte); |
| + output->push_back(second_byte); |
| + |
| + // Writes "extended payload length" field. |
| + if (second_byte == kPayloadLengthWithTwoByteExtendedLengthField) { |
| + uint16 payload_length_16 = static_cast<uint16>(header->payload_length); |
| + char encoded[sizeof(uint16)]; |
| + WriteBigEndian(encoded, payload_length_16); |
| + output->insert(output->end(), encoded, encoded + sizeof(uint16)); |
| + } else if (second_byte == kPayloadLengthWithEightByteExtendedLengthField) { |
| + char encoded[sizeof(uint64)]; |
| + WriteBigEndian(encoded, header->payload_length); |
| + output->insert(output->end(), encoded, encoded + sizeof(uint64)); |
| + } |
| + |
| + // Writes "masking key" field, if needed. |
| + if (header->masked) { |
| + output->insert(output->end(), masking_key_, |
| + masking_key_ + WebSocketFrameHeader::kMaskingKeyLength); |
| + } |
| + |
| + return true; |
| +} |
| + |
| +void WebSocketFrameBuilder::PinMaskingKeyForTesting(const char masking_key[]) { |
| + std::copy(masking_key, masking_key + WebSocketFrameHeader::kMaskingKeyLength, |
| + pinned_masking_key_for_testing_); |
| + has_pinned_masking_key_for_testing_ = true; |
| +} |
| + |
| +void WebSocketFrameBuilder::GenerateNewMaskingKey() { |
| + DCHECK(current_frame_header_->masked); |
| + |
| + static const size_t kMaskingKeyLength = |
| + WebSocketFrameHeader::kMaskingKeyLength; |
| + |
| + if (has_pinned_masking_key_for_testing_) { |
| + std::copy(pinned_masking_key_for_testing_, |
| + pinned_masking_key_for_testing_ + kMaskingKeyLength, |
| + masking_key_); |
| + } else { |
| + // Masking keys should be generated from a cryptographically secure random |
| + // number generator, which means web application authors should not be able |
| + // to guess the next value of masking key. |
| + base::RandBytes(masking_key_, kMaskingKeyLength); |
| + } |
| +} |
| + |
| +void WebSocketFrameBuilder::MaskPayload(std::vector<char>* output) { |
| + static const size_t kMaskingKeyLength = |
| + WebSocketFrameHeader::kMaskingKeyLength; |
| + |
| + // TODO(yutak): Make masking more efficient by XOR'ing every machine word |
| + // (4 or 8 bytes), instead of XOR'ing every byte. |
| + uint64 masking_key_offset = frame_offset_ % kMaskingKeyLength; |
| + for (std::vector<char>::iterator iter = output->begin(); |
| + iter != output->end(); ++iter) { |
| + *iter ^= masking_key_[masking_key_offset++]; |
| + if (masking_key_offset == kMaskingKeyLength) |
| + masking_key_offset = 0; |
| + } |
| +} |
| + |
| +void WebSocketFrameBuilder::Fail() { |
| + current_frame_header_.reset(); |
| + frame_offset_ = 0; |
| + failed_ = true; |
| +} |
| + |
| +} // namespace net |