Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(321)

Unified Diff: net/websockets/websocket_frame_builder.cc

Issue 10384180: Add functions used for building WebSocket frame data. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 8 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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

Powered by Google App Engine
This is Rietveld 408576698