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 |