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/spdy/spdy_header_block.h" | |
6 | |
7 #include <string.h> | |
8 | |
9 #include <algorithm> | |
10 #include <utility> | |
11 | |
12 #include "base/logging.h" | |
13 #include "base/macros.h" | |
14 #include "base/values.h" | |
15 #include "net/base/arena.h" | |
16 #include "net/http/http_log_util.h" | |
17 #include "net/log/net_log_capture_mode.h" | |
18 #include "net/spdy/platform/api/spdy_estimate_memory_usage.h" | |
19 #include "net/spdy/platform/api/spdy_string_utils.h" | |
20 | |
21 namespace net { | |
22 namespace { | |
23 | |
24 // By default, linked_hash_map's internal map allocates space for 100 map | |
25 // buckets on construction, which is larger than necessary. Standard library | |
26 // unordered map implementations use a list of prime numbers to set the bucket | |
27 // count for a particular capacity. |kInitialMapBuckets| is chosen to reduce | |
28 // memory usage for small header blocks, at the cost of having to rehash for | |
29 // large header blocks. | |
30 const size_t kInitialMapBuckets = 11; | |
31 | |
32 // SpdyHeaderBlock::Storage allocates blocks of this size by default. | |
33 const size_t kDefaultStorageBlockSize = 2048; | |
34 | |
35 const char kCookieKey[] = "cookie"; | |
36 const char kNullSeparator = 0; | |
37 | |
38 SpdyStringPiece SeparatorForKey(SpdyStringPiece key) { | |
39 if (key == kCookieKey) { | |
40 static SpdyStringPiece cookie_separator = "; "; | |
41 return cookie_separator; | |
42 } else { | |
43 return SpdyStringPiece(&kNullSeparator, 1); | |
44 } | |
45 } | |
46 | |
47 } // namespace | |
48 | |
49 // This class provides a backing store for SpdyStringPieces. It previously used | |
50 // custom allocation logic, but now uses an UnsafeArena instead. It has the | |
51 // property that SpdyStringPieces that refer to data in Storage are never | |
52 // invalidated until the Storage is deleted or Clear() is called. | |
53 // | |
54 // Write operations always append to the last block. If there is not enough | |
55 // space to perform the write, a new block is allocated, and any unused space | |
56 // is wasted. | |
57 class SpdyHeaderBlock::Storage { | |
58 public: | |
59 Storage() : arena_(kDefaultStorageBlockSize) {} | |
60 ~Storage() { Clear(); } | |
61 | |
62 SpdyStringPiece Write(const SpdyStringPiece s) { | |
63 return SpdyStringPiece(arena_.Memdup(s.data(), s.size()), s.size()); | |
64 } | |
65 | |
66 // If |s| points to the most recent allocation from arena_, the arena will | |
67 // reclaim the memory. Otherwise, this method is a no-op. | |
68 void Rewind(const SpdyStringPiece s) { | |
69 arena_.Free(const_cast<char*>(s.data()), s.size()); | |
70 } | |
71 | |
72 void Clear() { arena_.Reset(); } | |
73 | |
74 // Given a list of fragments and a separator, writes the fragments joined by | |
75 // the separator to a contiguous region of memory. Returns a SpdyStringPiece | |
76 // pointing to the region of memory. | |
77 SpdyStringPiece WriteFragments(const std::vector<SpdyStringPiece>& fragments, | |
78 SpdyStringPiece separator) { | |
79 if (fragments.empty()) { | |
80 return SpdyStringPiece(); | |
81 } | |
82 size_t total_size = separator.size() * (fragments.size() - 1); | |
83 for (const auto fragment : fragments) { | |
84 total_size += fragment.size(); | |
85 } | |
86 char* dst = arena_.Alloc(total_size); | |
87 size_t written = Join(dst, fragments, separator); | |
88 DCHECK_EQ(written, total_size); | |
89 return SpdyStringPiece(dst, total_size); | |
90 } | |
91 | |
92 size_t bytes_allocated() const { return arena_.status().bytes_allocated(); } | |
93 | |
94 // TODO(xunjieli): https://crbug.com/669108. Merge this with bytes_allocated() | |
95 size_t EstimateMemoryUsage() const { | |
96 return arena_.status().bytes_allocated(); | |
97 } | |
98 | |
99 private: | |
100 UnsafeArena arena_; | |
101 | |
102 DISALLOW_COPY_AND_ASSIGN(Storage); | |
103 }; | |
104 | |
105 SpdyHeaderBlock::HeaderValue::HeaderValue(Storage* storage, | |
106 SpdyStringPiece key, | |
107 SpdyStringPiece initial_value) | |
108 : storage_(storage), fragments_({initial_value}), pair_({key, {}}) {} | |
109 | |
110 SpdyHeaderBlock::HeaderValue::HeaderValue(HeaderValue&& other) | |
111 : storage_(other.storage_), | |
112 fragments_(std::move(other.fragments_)), | |
113 pair_(std::move(other.pair_)) {} | |
114 | |
115 SpdyHeaderBlock::HeaderValue& SpdyHeaderBlock::HeaderValue::operator=( | |
116 HeaderValue&& other) { | |
117 storage_ = other.storage_; | |
118 fragments_ = std::move(other.fragments_); | |
119 pair_ = std::move(other.pair_); | |
120 return *this; | |
121 } | |
122 | |
123 SpdyHeaderBlock::HeaderValue::~HeaderValue() {} | |
124 | |
125 SpdyStringPiece SpdyHeaderBlock::HeaderValue::ConsolidatedValue() const { | |
126 if (fragments_.empty()) { | |
127 return SpdyStringPiece(); | |
128 } | |
129 if (fragments_.size() > 1) { | |
130 fragments_ = { | |
131 storage_->WriteFragments(fragments_, SeparatorForKey(pair_.first))}; | |
132 } | |
133 return fragments_[0]; | |
134 } | |
135 | |
136 void SpdyHeaderBlock::HeaderValue::Append(SpdyStringPiece fragment) { | |
137 fragments_.push_back(fragment); | |
138 } | |
139 | |
140 const std::pair<SpdyStringPiece, SpdyStringPiece>& | |
141 SpdyHeaderBlock::HeaderValue::as_pair() const { | |
142 pair_.second = ConsolidatedValue(); | |
143 return pair_; | |
144 } | |
145 | |
146 SpdyHeaderBlock::iterator::iterator(MapType::const_iterator it) : it_(it) {} | |
147 | |
148 SpdyHeaderBlock::iterator::iterator(const iterator& other) : it_(other.it_) {} | |
149 | |
150 SpdyHeaderBlock::iterator::~iterator() {} | |
151 | |
152 SpdyHeaderBlock::ValueProxy::ValueProxy( | |
153 SpdyHeaderBlock::MapType* block, | |
154 SpdyHeaderBlock::Storage* storage, | |
155 SpdyHeaderBlock::MapType::iterator lookup_result, | |
156 const SpdyStringPiece key) | |
157 : block_(block), | |
158 storage_(storage), | |
159 lookup_result_(lookup_result), | |
160 key_(key), | |
161 valid_(true) {} | |
162 | |
163 SpdyHeaderBlock::ValueProxy::ValueProxy(ValueProxy&& other) | |
164 : block_(other.block_), | |
165 storage_(other.storage_), | |
166 lookup_result_(other.lookup_result_), | |
167 key_(other.key_), | |
168 valid_(true) { | |
169 other.valid_ = false; | |
170 } | |
171 | |
172 SpdyHeaderBlock::ValueProxy& SpdyHeaderBlock::ValueProxy::operator=( | |
173 SpdyHeaderBlock::ValueProxy&& other) { | |
174 block_ = other.block_; | |
175 storage_ = other.storage_; | |
176 lookup_result_ = other.lookup_result_; | |
177 key_ = other.key_; | |
178 valid_ = true; | |
179 other.valid_ = false; | |
180 return *this; | |
181 } | |
182 | |
183 SpdyHeaderBlock::ValueProxy::~ValueProxy() { | |
184 // If the ValueProxy is destroyed while lookup_result_ == block_->end(), | |
185 // the assignment operator was never used, and the block's Storage can | |
186 // reclaim the memory used by the key. This makes lookup-only access to | |
187 // SpdyHeaderBlock through operator[] memory-neutral. | |
188 if (valid_ && lookup_result_ == block_->end()) { | |
189 storage_->Rewind(key_); | |
190 } | |
191 } | |
192 | |
193 SpdyHeaderBlock::ValueProxy& SpdyHeaderBlock::ValueProxy::operator=( | |
194 const SpdyStringPiece value) { | |
195 if (lookup_result_ == block_->end()) { | |
196 DVLOG(1) << "Inserting: (" << key_ << ", " << value << ")"; | |
197 lookup_result_ = | |
198 block_ | |
199 ->emplace(std::make_pair( | |
200 key_, HeaderValue(storage_, key_, storage_->Write(value)))) | |
201 .first; | |
202 } else { | |
203 DVLOG(1) << "Updating key: " << key_ << " with value: " << value; | |
204 lookup_result_->second = | |
205 HeaderValue(storage_, key_, storage_->Write(value)); | |
206 } | |
207 return *this; | |
208 } | |
209 | |
210 SpdyString SpdyHeaderBlock::ValueProxy::as_string() const { | |
211 if (lookup_result_ == block_->end()) { | |
212 return ""; | |
213 } else { | |
214 return SpdyString(lookup_result_->second.value()); | |
215 } | |
216 } | |
217 | |
218 SpdyHeaderBlock::SpdyHeaderBlock() : block_(kInitialMapBuckets) {} | |
219 | |
220 SpdyHeaderBlock::SpdyHeaderBlock(SpdyHeaderBlock&& other) = default; | |
221 | |
222 SpdyHeaderBlock::~SpdyHeaderBlock() {} | |
223 | |
224 SpdyHeaderBlock& SpdyHeaderBlock::operator=(SpdyHeaderBlock&& other) { | |
225 block_.swap(other.block_); | |
226 storage_.swap(other.storage_); | |
227 return *this; | |
228 } | |
229 | |
230 SpdyHeaderBlock SpdyHeaderBlock::Clone() const { | |
231 SpdyHeaderBlock copy; | |
232 for (const auto& p : *this) { | |
233 copy.AppendHeader(p.first, p.second); | |
234 } | |
235 return copy; | |
236 } | |
237 | |
238 bool SpdyHeaderBlock::operator==(const SpdyHeaderBlock& other) const { | |
239 return size() == other.size() && std::equal(begin(), end(), other.begin()); | |
240 } | |
241 | |
242 bool SpdyHeaderBlock::operator!=(const SpdyHeaderBlock& other) const { | |
243 return !(operator==(other)); | |
244 } | |
245 | |
246 SpdyString SpdyHeaderBlock::DebugString() const { | |
247 if (empty()) { | |
248 return "{}"; | |
249 } | |
250 | |
251 SpdyString output = "\n{\n"; | |
252 for (auto it = begin(); it != end(); ++it) { | |
253 SpdyStrAppend(&output, " ", it->first, " ", it->second, "\n"); | |
254 } | |
255 SpdyStrAppend(&output, "}\n"); | |
256 return output; | |
257 } | |
258 | |
259 void SpdyHeaderBlock::clear() { | |
260 block_.clear(); | |
261 storage_.reset(); | |
262 } | |
263 | |
264 void SpdyHeaderBlock::insert(const SpdyHeaderBlock::value_type& value) { | |
265 // TODO(birenroy): Write new value in place of old value, if it fits. | |
266 auto iter = block_.find(value.first); | |
267 if (iter == block_.end()) { | |
268 DVLOG(1) << "Inserting: (" << value.first << ", " << value.second << ")"; | |
269 AppendHeader(value.first, value.second); | |
270 } else { | |
271 DVLOG(1) << "Updating key: " << iter->first | |
272 << " with value: " << value.second; | |
273 auto* storage = GetStorage(); | |
274 iter->second = | |
275 HeaderValue(storage, iter->first, storage->Write(value.second)); | |
276 } | |
277 } | |
278 | |
279 SpdyHeaderBlock::ValueProxy SpdyHeaderBlock::operator[]( | |
280 const SpdyStringPiece key) { | |
281 DVLOG(2) << "Operator[] saw key: " << key; | |
282 SpdyStringPiece out_key; | |
283 auto iter = block_.find(key); | |
284 if (iter == block_.end()) { | |
285 // We write the key first, to assure that the ValueProxy has a | |
286 // reference to a valid SpdyStringPiece in its operator=. | |
287 out_key = GetStorage()->Write(key); | |
288 DVLOG(2) << "Key written as: " << std::hex | |
289 << static_cast<const void*>(key.data()) << ", " << std::dec | |
290 << key.size(); | |
291 } else { | |
292 out_key = iter->first; | |
293 } | |
294 return ValueProxy(&block_, GetStorage(), iter, out_key); | |
295 } | |
296 | |
297 void SpdyHeaderBlock::AppendValueOrAddHeader(const SpdyStringPiece key, | |
298 const SpdyStringPiece value) { | |
299 auto iter = block_.find(key); | |
300 if (iter == block_.end()) { | |
301 DVLOG(1) << "Inserting: (" << key << ", " << value << ")"; | |
302 AppendHeader(key, value); | |
303 return; | |
304 } | |
305 DVLOG(1) << "Updating key: " << iter->first << "; appending value: " << value; | |
306 iter->second.Append(GetStorage()->Write(value)); | |
307 } | |
308 | |
309 size_t SpdyHeaderBlock::EstimateMemoryUsage() const { | |
310 // TODO(xunjieli): https://crbug.com/669108. Also include |block_| when EMU() | |
311 // supports linked_hash_map. | |
312 return SpdyEstimateMemoryUsage(storage_); | |
313 } | |
314 | |
315 void SpdyHeaderBlock::AppendHeader(const SpdyStringPiece key, | |
316 const SpdyStringPiece value) { | |
317 auto* storage = GetStorage(); | |
318 auto backed_key = storage->Write(key); | |
319 block_.emplace(std::make_pair( | |
320 backed_key, HeaderValue(storage, backed_key, storage->Write(value)))); | |
321 } | |
322 | |
323 SpdyHeaderBlock::Storage* SpdyHeaderBlock::GetStorage() { | |
324 if (!storage_) { | |
325 storage_.reset(new Storage); | |
326 } | |
327 return storage_.get(); | |
328 } | |
329 | |
330 std::unique_ptr<base::Value> SpdyHeaderBlockNetLogCallback( | |
331 const SpdyHeaderBlock* headers, | |
332 NetLogCaptureMode capture_mode) { | |
333 std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue()); | |
334 base::DictionaryValue* headers_dict = new base::DictionaryValue(); | |
335 for (SpdyHeaderBlock::const_iterator it = headers->begin(); | |
336 it != headers->end(); ++it) { | |
337 headers_dict->SetWithoutPathExpansion( | |
338 it->first.as_string(), | |
339 new base::Value(ElideHeaderValueForNetLog( | |
340 capture_mode, it->first.as_string(), it->second.as_string()))); | |
341 } | |
342 dict->Set("headers", headers_dict); | |
343 return std::move(dict); | |
344 } | |
345 | |
346 bool SpdyHeaderBlockFromNetLogParam( | |
347 const base::Value* event_param, | |
348 SpdyHeaderBlock* headers) { | |
349 headers->clear(); | |
350 | |
351 const base::DictionaryValue* dict = NULL; | |
352 const base::DictionaryValue* header_dict = NULL; | |
353 | |
354 if (!event_param || | |
355 !event_param->GetAsDictionary(&dict) || | |
356 !dict->GetDictionary("headers", &header_dict)) { | |
357 return false; | |
358 } | |
359 | |
360 for (base::DictionaryValue::Iterator it(*header_dict); !it.IsAtEnd(); | |
361 it.Advance()) { | |
362 SpdyString value; | |
363 if (!it.value().GetAsString(&value)) { | |
364 headers->clear(); | |
365 return false; | |
366 } | |
367 (*headers)[it.key()] = value; | |
368 } | |
369 return true; | |
370 } | |
371 | |
372 size_t SpdyHeaderBlock::bytes_allocated() const { | |
373 if (storage_ == nullptr) { | |
374 return 0; | |
375 } else { | |
376 return storage_->bytes_allocated(); | |
377 } | |
378 } | |
379 | |
380 size_t Join(char* dst, | |
381 const std::vector<SpdyStringPiece>& fragments, | |
382 SpdyStringPiece separator) { | |
383 if (fragments.empty()) { | |
384 return 0; | |
385 } | |
386 auto* original_dst = dst; | |
387 auto it = fragments.begin(); | |
388 memcpy(dst, it->data(), it->size()); | |
389 dst += it->size(); | |
390 for (++it; it != fragments.end(); ++it) { | |
391 memcpy(dst, separator.data(), separator.size()); | |
392 dst += separator.size(); | |
393 memcpy(dst, it->data(), it->size()); | |
394 dst += it->size(); | |
395 } | |
396 return dst - original_dst; | |
397 } | |
398 | |
399 } // namespace net | |
OLD | NEW |