Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "net/websockets/websocket_frame_builder.h" | |
| 6 | |
| 7 #include <algorithm> | |
| 8 | |
| 9 #include "base/basictypes.h" | |
| 10 #include "base/logging.h" | |
| 11 #include "base/memory/scoped_ptr.h" | |
| 12 #include "base/memory/scoped_vector.h" | |
| 13 #include "base/rand_util.h" | |
| 14 #include "net/base/big_endian.h" | |
| 15 #include "net/websockets/websocket_frame.h" | |
| 16 | |
| 17 namespace { | |
| 18 | |
| 19 const uint8 kFinalBit = 0x80; | |
| 20 const uint8 kReserved1Bit = 0x40; | |
| 21 const uint8 kReserved2Bit = 0x20; | |
| 22 const uint8 kReserved3Bit = 0x10; | |
| 23 const uint8 kOpCodeMask = 0xF; | |
| 24 const uint8 kMaskBit = 0x80; | |
| 25 const uint64 kMaxPayloadLengthWithoutExtendedLengthField = 125; | |
| 26 const uint64 kPayloadLengthWithTwoByteExtendedLengthField = 126; | |
| 27 const uint64 kPayloadLengthWithEightByteExtendedLengthField = 127; | |
| 28 | |
| 29 } // Unnamed namespace. | |
| 30 | |
| 31 namespace net { | |
| 32 | |
| 33 WebSocketFrameBuilder::WebSocketFrameBuilder() | |
| 34 : frame_offset_(0), | |
| 35 has_pinned_masking_key_for_testing_(false), | |
| 36 failed_(false) { | |
| 37 std::fill(masking_key_, | |
| 38 masking_key_ + WebSocketFrameHeader::kMaskingKeyLength, | |
| 39 '\0'); | |
| 40 std::fill( | |
| 41 pinned_masking_key_for_testing_, | |
| 42 pinned_masking_key_for_testing_ + WebSocketFrameHeader::kMaskingKeyLength, | |
| 43 '\0'); | |
| 44 } | |
| 45 | |
| 46 WebSocketFrameBuilder::~WebSocketFrameBuilder() { | |
| 47 } | |
| 48 | |
| 49 bool WebSocketFrameBuilder::Encode( | |
| 50 ScopedVector<WebSocketFrameChunk> frame_chunks, | |
| 51 std::vector<char>* output) { | |
| 52 if (failed_) | |
| 53 return false; | |
| 54 | |
| 55 // 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
| |
| 56 // or missing |header| in the first chunk, because these are programming | |
| 57 // errors we want to avoid. When DCHECK isn't functional (i.e. Release and | |
| 58 // !DCHECK_ALWAYS_ON), we fail gracefully on these errors. | |
| 59 for (ScopedVector<WebSocketFrameChunk>::iterator iter = frame_chunks.begin(); | |
| 60 iter != frame_chunks.end(); ++iter) { | |
| 61 WebSocketFrameChunk* chunk = *iter; | |
| 62 if (chunk->header.get()) { // Beginning of a new frame. | |
| 63 DCHECK(!current_frame_header_.get()); | |
| 64 DCHECK_EQ(0u, frame_offset_); | |
| 65 if (current_frame_header_.get() || frame_offset_ != 0u) { | |
| 66 Fail(); | |
| 67 return false; | |
| 68 } | |
| 69 current_frame_header_ = chunk->header.Pass(); | |
| 70 if (current_frame_header_->masked) | |
| 71 GenerateNewMaskingKey(); | |
| 72 if (!EncodeFrameHeader(current_frame_header_.get(), output)) { | |
| 73 Fail(); | |
| 74 return false; | |
| 75 } | |
| 76 } | |
| 77 | |
| 78 DCHECK(current_frame_header_.get()); | |
| 79 if (!current_frame_header_.get()) { | |
| 80 Fail(); | |
| 81 return false; | |
| 82 } | |
| 83 | |
| 84 if (current_frame_header_->masked) | |
|
mmenke
2012/05/15 18:36:31
According to specs, it's invalid for this to be fa
| |
| 85 MaskPayload(&chunk->data); | |
| 86 // FIXME(yutak): Remove copy here. Ultimately, we probably need to have | |
| 87 // "gather writes" ability (aka vectored I/O) in the Socket interface | |
| 88 // so we can send data from multiple local buffers without data copies. | |
| 89 output->insert(output->end(), chunk->data.begin(), chunk->data.end()); | |
| 90 frame_offset_ += chunk->data.size(); | |
| 91 DCHECK_LE(frame_offset_, current_frame_header_->payload_length); | |
| 92 if (frame_offset_ > current_frame_header_->payload_length) { | |
| 93 Fail(); | |
| 94 return false; | |
| 95 } | |
| 96 | |
| 97 if (chunk->final_chunk) { | |
| 98 // Throw away the information about the current frame to get ready | |
| 99 // for the next frame. | |
| 100 DCHECK_EQ(frame_offset_, current_frame_header_->payload_length); | |
| 101 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
| |
| 102 Fail(); | |
| 103 return false; | |
| 104 } | |
| 105 current_frame_header_.reset(); | |
| 106 frame_offset_ = 0; | |
| 107 } | |
| 108 } | |
| 109 | |
| 110 return true; | |
| 111 } | |
| 112 | |
| 113 bool WebSocketFrameBuilder::EncodeFrameHeader( | |
| 114 WebSocketFrameHeader* header, | |
| 115 std::vector<char>* output) const { | |
| 116 DCHECK(header); | |
| 117 | |
| 118 // header->opcode must fit to kOpCodeMask. | |
| 119 DCHECK((header->opcode & kOpCodeMask) == header->opcode); | |
| 120 | |
| 121 uint8 first_byte = 0u; | |
| 122 first_byte |= header->final ? kFinalBit : 0u; | |
| 123 first_byte |= header->reserved1 ? kReserved1Bit : 0u; | |
| 124 first_byte |= header->reserved2 ? kReserved2Bit : 0u; | |
| 125 first_byte |= header->reserved3 ? kReserved3Bit : 0u; | |
| 126 first_byte |= header->opcode; | |
| 127 | |
| 128 uint8 second_byte = 0; | |
|
mmenke
2012/05/15 18:36:31
nit: 0u
| |
| 129 second_byte |= header->masked ? kMaskBit : 0u; | |
| 130 if (header->payload_length <= | |
| 131 kMaxPayloadLengthWithoutExtendedLengthField) { | |
| 132 second_byte |= header->payload_length; | |
| 133 } else if (header->payload_length <= kuint16max) { | |
| 134 second_byte |= kPayloadLengthWithTwoByteExtendedLengthField; | |
| 135 } else if (header->payload_length <= static_cast<uint64>(kint64max)) { | |
| 136 second_byte |= kPayloadLengthWithEightByteExtendedLengthField; | |
| 137 } else { | |
| 138 // WebSocket protocol specification doesn't allow payload length to | |
| 139 // exceed kint64max (0x7FFFFFFFFFFFFFFF). | |
| 140 return false; | |
| 141 } | |
| 142 | |
| 143 output->push_back(first_byte); | |
| 144 output->push_back(second_byte); | |
| 145 | |
| 146 // Writes "extended payload length" field. | |
| 147 if (second_byte == kPayloadLengthWithTwoByteExtendedLengthField) { | |
| 148 uint16 payload_length_16 = static_cast<uint16>(header->payload_length); | |
| 149 char encoded[sizeof(uint16)]; | |
| 150 WriteBigEndian(encoded, payload_length_16); | |
| 151 output->insert(output->end(), encoded, encoded + sizeof(uint16)); | |
| 152 } else if (second_byte == kPayloadLengthWithEightByteExtendedLengthField) { | |
| 153 char encoded[sizeof(uint64)]; | |
| 154 WriteBigEndian(encoded, header->payload_length); | |
| 155 output->insert(output->end(), encoded, encoded + sizeof(uint64)); | |
| 156 } | |
| 157 | |
| 158 // Writes "masking key" field, if needed. | |
| 159 if (header->masked) { | |
| 160 output->insert(output->end(), masking_key_, | |
| 161 masking_key_ + WebSocketFrameHeader::kMaskingKeyLength); | |
| 162 } | |
| 163 | |
| 164 return true; | |
| 165 } | |
| 166 | |
| 167 void WebSocketFrameBuilder::PinMaskingKeyForTesting(const char masking_key[]) { | |
| 168 std::copy(masking_key, masking_key + WebSocketFrameHeader::kMaskingKeyLength, | |
| 169 pinned_masking_key_for_testing_); | |
| 170 has_pinned_masking_key_for_testing_ = true; | |
| 171 } | |
| 172 | |
| 173 void WebSocketFrameBuilder::GenerateNewMaskingKey() { | |
| 174 DCHECK(current_frame_header_->masked); | |
| 175 | |
| 176 static const size_t kMaskingKeyLength = | |
| 177 WebSocketFrameHeader::kMaskingKeyLength; | |
| 178 | |
| 179 if (has_pinned_masking_key_for_testing_) { | |
| 180 std::copy(pinned_masking_key_for_testing_, | |
| 181 pinned_masking_key_for_testing_ + kMaskingKeyLength, | |
| 182 masking_key_); | |
| 183 } else { | |
| 184 // Masking keys should be generated from a cryptographically secure random | |
| 185 // number generator, which means web application authors should not be able | |
| 186 // to guess the next value of masking key. | |
| 187 base::RandBytes(masking_key_, kMaskingKeyLength); | |
| 188 } | |
| 189 } | |
| 190 | |
| 191 void WebSocketFrameBuilder::MaskPayload(std::vector<char>* output) { | |
| 192 static const size_t kMaskingKeyLength = | |
| 193 WebSocketFrameHeader::kMaskingKeyLength; | |
| 194 | |
| 195 // TODO(yutak): Make masking more efficient by XOR'ing every machine word | |
| 196 // (4 or 8 bytes), instead of XOR'ing every byte. | |
| 197 uint64 masking_key_offset = frame_offset_ % kMaskingKeyLength; | |
| 198 for (std::vector<char>::iterator iter = output->begin(); | |
| 199 iter != output->end(); ++iter) { | |
| 200 *iter ^= masking_key_[masking_key_offset++]; | |
| 201 if (masking_key_offset == kMaskingKeyLength) | |
| 202 masking_key_offset = 0; | |
| 203 } | |
| 204 } | |
| 205 | |
| 206 void WebSocketFrameBuilder::Fail() { | |
| 207 current_frame_header_.reset(); | |
| 208 frame_offset_ = 0; | |
| 209 failed_ = true; | |
| 210 } | |
| 211 | |
| 212 } // namespace net | |
| OLD | NEW |