OLD | NEW |
(Empty) | |
| 1 // Copyright 2016 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 "mojo/edk/system/channel.h" |
| 6 |
| 7 #include <string.h> |
| 8 |
| 9 #include <algorithm> |
| 10 #include <limits> |
| 11 |
| 12 #include "base/macros.h" |
| 13 #include "base/memory/aligned_memory.h" |
| 14 #include "mojo/edk/embedder/platform_handle.h" |
| 15 |
| 16 namespace mojo { |
| 17 namespace edk { |
| 18 |
| 19 namespace { |
| 20 |
| 21 static_assert(sizeof(Channel::Message::Header) % kChannelMessageAlignment == 0, |
| 22 "Invalid Header size."); |
| 23 |
| 24 } // namespace |
| 25 |
| 26 const size_t kReadBufferSize = 4096; |
| 27 const size_t kMaxUnusedReadBufferCapacity = 256 * 1024; |
| 28 const size_t kMaxChannelMessageSize = 256 * 1024 * 1024; |
| 29 |
| 30 Channel::Message::Message(size_t payload_size, size_t num_handles) { |
| 31 size_ = payload_size + sizeof(Header); |
| 32 #if defined(OS_WIN) |
| 33 // On Windows we serialize platform handles directly into the message buffer. |
| 34 size_ += num_handles * sizeof(PlatformHandle); |
| 35 #endif |
| 36 |
| 37 data_ = static_cast<char*>(base::AlignedAlloc(size_, |
| 38 kChannelMessageAlignment)); |
| 39 header_ = reinterpret_cast<Header*>(data_); |
| 40 |
| 41 DCHECK_LE(size_, std::numeric_limits<uint32_t>::max()); |
| 42 header_->num_bytes = static_cast<uint32_t>(size_); |
| 43 |
| 44 DCHECK_LE(num_handles, std::numeric_limits<uint16_t>::max()); |
| 45 header_->num_handles = static_cast<uint16_t>(num_handles); |
| 46 |
| 47 header_->padding = 0; |
| 48 |
| 49 #if defined(OS_WIN) |
| 50 if (num_handles > 0) { |
| 51 handles_ = reinterpret_cast<PlatformHandle*>( |
| 52 data_ + sizeof(Header) + payload_size); |
| 53 // Initialize all handles to invalid values. |
| 54 for (size_t i = 0; i < header_->num_handles; ++i) |
| 55 handles()[i] = PlatformHandle(); |
| 56 } |
| 57 #endif |
| 58 } |
| 59 |
| 60 Channel::Message::~Message() { |
| 61 #if defined(OS_WIN) |
| 62 // On POSIX the ScopedPlatformHandleVectorPtr will do this for us. |
| 63 for (size_t i = 0; i < header_->num_handles; ++i) |
| 64 handles()[i].CloseIfNecessary(); |
| 65 #endif |
| 66 base::AlignedFree(data_); |
| 67 } |
| 68 |
| 69 // static |
| 70 Channel::MessagePtr Channel::Message::Deserialize(const void* data, |
| 71 size_t data_num_bytes) { |
| 72 #if !defined(OS_WIN) |
| 73 // We only serialize messages into other messages when performing message |
| 74 // relay on Windows. |
| 75 NOTREACHED(); |
| 76 #endif |
| 77 if (data_num_bytes < sizeof(Header)) |
| 78 return nullptr; |
| 79 |
| 80 const Header* header = reinterpret_cast<const Header*>(data); |
| 81 if (header->num_bytes != data_num_bytes) { |
| 82 DLOG(ERROR) << "Decoding invalid message: " << header->num_bytes |
| 83 << " != " << data_num_bytes; |
| 84 return nullptr; |
| 85 } |
| 86 |
| 87 uint32_t handles_size = header->num_handles * sizeof(PlatformHandle); |
| 88 if (data_num_bytes < sizeof(Header) + handles_size) { |
| 89 DLOG(ERROR) << "Decoding invalid message:" << data_num_bytes |
| 90 << " < " << (sizeof(Header) + handles_size); |
| 91 return nullptr; |
| 92 } |
| 93 |
| 94 DCHECK_LE(handles_size, data_num_bytes - sizeof(Header)); |
| 95 |
| 96 MessagePtr message(new Message(data_num_bytes - sizeof(Header) - handles_size, |
| 97 header->num_handles)); |
| 98 |
| 99 DCHECK_EQ(message->data_num_bytes(), data_num_bytes); |
| 100 |
| 101 // Copy all bytes, including the serialized handles. |
| 102 memcpy(message->mutable_payload(), |
| 103 static_cast<const char*>(data) + sizeof(Header), |
| 104 data_num_bytes - sizeof(Header)); |
| 105 |
| 106 return message; |
| 107 } |
| 108 |
| 109 size_t Channel::Message::payload_size() const { |
| 110 #if defined(OS_WIN) |
| 111 return size_ - sizeof(Header) - |
| 112 sizeof(PlatformHandle) * header_->num_handles; |
| 113 #else |
| 114 return header_->num_bytes - sizeof(Header); |
| 115 #endif |
| 116 } |
| 117 |
| 118 PlatformHandle* Channel::Message::handles() { |
| 119 if (header_->num_handles == 0) |
| 120 return nullptr; |
| 121 #if defined(OS_WIN) |
| 122 return reinterpret_cast<PlatformHandle*>(static_cast<char*>(data_) + |
| 123 sizeof(Header) + payload_size()); |
| 124 #else |
| 125 CHECK(handle_vector_); |
| 126 return handle_vector_->data(); |
| 127 #endif |
| 128 } |
| 129 |
| 130 void Channel::Message::SetHandles(ScopedPlatformHandleVectorPtr new_handles) { |
| 131 if (header_->num_handles == 0) { |
| 132 CHECK(!new_handles || new_handles->size() == 0); |
| 133 return; |
| 134 } |
| 135 |
| 136 CHECK(new_handles && new_handles->size() == header_->num_handles); |
| 137 #if defined(OS_WIN) |
| 138 memcpy(handles(), new_handles->data(), |
| 139 sizeof(PlatformHandle) * header_->num_handles); |
| 140 new_handles->clear(); |
| 141 #else |
| 142 std::swap(handle_vector_, new_handles); |
| 143 #endif |
| 144 } |
| 145 |
| 146 ScopedPlatformHandleVectorPtr Channel::Message::TakeHandles() { |
| 147 #if defined(OS_WIN) |
| 148 if (header_->num_handles == 0) |
| 149 return ScopedPlatformHandleVectorPtr(); |
| 150 ScopedPlatformHandleVectorPtr moved_handles( |
| 151 new PlatformHandleVector(header_->num_handles)); |
| 152 for (size_t i = 0; i < header_->num_handles; ++i) |
| 153 std::swap(moved_handles->at(i), handles()[i]); |
| 154 return moved_handles; |
| 155 #else |
| 156 return std::move(handle_vector_); |
| 157 #endif |
| 158 } |
| 159 |
| 160 // Helper class for managing a Channel's read buffer allocations. This maintains |
| 161 // a single contiguous buffer with the layout: |
| 162 // |
| 163 // [discarded bytes][occupied bytes][unoccupied bytes] |
| 164 // |
| 165 // The Reserve() method ensures that a certain capacity of unoccupied bytes are |
| 166 // available. It does not claim that capacity and only allocates new capacity |
| 167 // when strictly necessary. |
| 168 // |
| 169 // Claim() marks unoccupied bytes as occupied. |
| 170 // |
| 171 // Discard() marks occupied bytes as discarded, signifying that their contents |
| 172 // can be forgotten or overwritten. |
| 173 // |
| 174 // The most common Channel behavior in practice should result in very few |
| 175 // allocations and copies, as memory is claimed and discarded shortly after |
| 176 // being reserved, and future reservations will immediately reuse discarded |
| 177 // memory. |
| 178 class Channel::ReadBuffer { |
| 179 public: |
| 180 ReadBuffer() { |
| 181 size_ = kReadBufferSize; |
| 182 data_ = static_cast<char*>(base::AlignedAlloc(size_, |
| 183 kChannelMessageAlignment)); |
| 184 } |
| 185 |
| 186 ~ReadBuffer() { |
| 187 DCHECK(data_); |
| 188 base::AlignedFree(data_); |
| 189 } |
| 190 |
| 191 const char* occupied_bytes() const { return data_ + num_discarded_bytes_; } |
| 192 |
| 193 size_t num_occupied_bytes() const { |
| 194 return num_occupied_bytes_ - num_discarded_bytes_; |
| 195 } |
| 196 |
| 197 // Ensures the ReadBuffer has enough contiguous space allocated to hold |
| 198 // |num_bytes| more bytes; returns the address of the first available byte. |
| 199 char* Reserve(size_t num_bytes) { |
| 200 if (num_occupied_bytes_ + num_bytes > size_) { |
| 201 size_ = std::max(size_ * 2, num_occupied_bytes_ + num_bytes); |
| 202 void* new_data = base::AlignedAlloc(size_, kChannelMessageAlignment); |
| 203 memcpy(new_data, data_, num_occupied_bytes_); |
| 204 base::AlignedFree(data_); |
| 205 data_ = static_cast<char*>(new_data); |
| 206 } |
| 207 |
| 208 return data_ + num_occupied_bytes_; |
| 209 } |
| 210 |
| 211 // Marks the first |num_bytes| unoccupied bytes as occupied. |
| 212 void Claim(size_t num_bytes) { |
| 213 DCHECK_LE(num_occupied_bytes_ + num_bytes, size_); |
| 214 num_occupied_bytes_ += num_bytes; |
| 215 } |
| 216 |
| 217 // Marks the first |num_bytes| occupied bytes as discarded. This may result in |
| 218 // shrinkage of the internal buffer, and it is not safe to assume the result |
| 219 // of a previous Reserve() call is still valid after this. |
| 220 void Discard(size_t num_bytes) { |
| 221 DCHECK_LE(num_discarded_bytes_ + num_bytes, num_occupied_bytes_); |
| 222 num_discarded_bytes_ += num_bytes; |
| 223 |
| 224 if (num_discarded_bytes_ == num_occupied_bytes_) { |
| 225 // We can just reuse the buffer from the beginning in this common case. |
| 226 num_discarded_bytes_ = 0; |
| 227 num_occupied_bytes_ = 0; |
| 228 } |
| 229 |
| 230 if (num_discarded_bytes_ > kMaxUnusedReadBufferCapacity) { |
| 231 // In the uncommon case that we have a lot of discarded data at the |
| 232 // front of the buffer, simply move remaining data to a smaller buffer. |
| 233 size_t num_preserved_bytes = num_occupied_bytes_ - num_discarded_bytes_; |
| 234 size_ = std::max(num_preserved_bytes, kReadBufferSize); |
| 235 char* new_data = static_cast<char*>( |
| 236 base::AlignedAlloc(size_, kChannelMessageAlignment)); |
| 237 memcpy(new_data, data_ + num_discarded_bytes_, num_preserved_bytes); |
| 238 base::AlignedFree(data_); |
| 239 data_ = new_data; |
| 240 num_discarded_bytes_ = 0; |
| 241 num_occupied_bytes_ = num_preserved_bytes; |
| 242 } |
| 243 |
| 244 // TODO: we should also adaptively shrink the buffer in case of the |
| 245 // occasional abnormally large read. |
| 246 } |
| 247 |
| 248 private: |
| 249 char* data_ = nullptr; |
| 250 |
| 251 // The total size of the allocated buffer. |
| 252 size_t size_ = 0; |
| 253 |
| 254 // The number of discarded bytes at the beginning of the allocated buffer. |
| 255 size_t num_discarded_bytes_ = 0; |
| 256 |
| 257 // The total number of occupied bytes, including discarded bytes. |
| 258 size_t num_occupied_bytes_ = 0; |
| 259 |
| 260 DISALLOW_COPY_AND_ASSIGN(ReadBuffer); |
| 261 }; |
| 262 |
| 263 Channel::Channel(Delegate* delegate) |
| 264 : delegate_(delegate), read_buffer_(new ReadBuffer) { |
| 265 } |
| 266 |
| 267 Channel::~Channel() { |
| 268 } |
| 269 |
| 270 void Channel::ShutDown() { |
| 271 delegate_ = nullptr; |
| 272 ShutDownImpl(); |
| 273 } |
| 274 |
| 275 char* Channel::GetReadBuffer(size_t *buffer_capacity) { |
| 276 DCHECK(read_buffer_); |
| 277 size_t required_capacity = *buffer_capacity; |
| 278 if (!required_capacity) |
| 279 required_capacity = kReadBufferSize; |
| 280 |
| 281 *buffer_capacity = required_capacity; |
| 282 return read_buffer_->Reserve(required_capacity); |
| 283 } |
| 284 |
| 285 bool Channel::OnReadComplete(size_t bytes_read, size_t *next_read_size_hint) { |
| 286 bool did_dispatch_message = false; |
| 287 read_buffer_->Claim(bytes_read); |
| 288 while (read_buffer_->num_occupied_bytes() >= sizeof(Message::Header)) { |
| 289 // We have at least enough data available for a MessageHeader. |
| 290 const Message::Header* header = reinterpret_cast<const Message::Header*>( |
| 291 read_buffer_->occupied_bytes()); |
| 292 if (header->num_bytes < sizeof(Message::Header) || |
| 293 header->num_bytes > kMaxChannelMessageSize) { |
| 294 LOG(ERROR) << "Invalid message size: " << header->num_bytes; |
| 295 return false; |
| 296 } |
| 297 |
| 298 if (read_buffer_->num_occupied_bytes() < header->num_bytes) { |
| 299 // Not enough data available to read the full message. Hint to the |
| 300 // implementation that it should try reading the full size of the message. |
| 301 *next_read_size_hint = |
| 302 header->num_bytes - read_buffer_->num_occupied_bytes(); |
| 303 return true; |
| 304 } |
| 305 |
| 306 size_t payload_size = header->num_bytes - sizeof(Message::Header); |
| 307 void* payload = payload_size ? const_cast<Message::Header*>(&header[1]) |
| 308 : nullptr; |
| 309 |
| 310 ScopedPlatformHandleVectorPtr handles; |
| 311 if (header->num_handles > 0) { |
| 312 handles = GetReadPlatformHandles(header->num_handles, |
| 313 &payload, &payload_size); |
| 314 if (!handles) { |
| 315 // Not enough handles available for this message. |
| 316 break; |
| 317 } |
| 318 } |
| 319 |
| 320 // We've got a complete message! Dispatch it and try another. |
| 321 if (delegate_) { |
| 322 delegate_->OnChannelMessage(payload, payload_size, std::move(handles)); |
| 323 did_dispatch_message = true; |
| 324 } |
| 325 |
| 326 read_buffer_->Discard(header->num_bytes); |
| 327 } |
| 328 |
| 329 *next_read_size_hint = did_dispatch_message ? 0 : kReadBufferSize; |
| 330 return true; |
| 331 } |
| 332 |
| 333 void Channel::OnError() { |
| 334 if (delegate_) |
| 335 delegate_->OnChannelError(); |
| 336 } |
| 337 |
| 338 } // namespace edk |
| 339 } // namespace mojo |
OLD | NEW |