1 // Copyright 2014 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/hpack_encoder.h" 6 7 #include <algorithm> 8 9 #include "base/logging.h" 10 #include "net/spdy/hpack_header_table.h" 11 #include "net/spdy/hpack_huffman_table.h" 12 #include "net/spdy/hpack_output_stream.h" 13 14 namespace net { 15 16 using base::StringPiece; 17 using std::string; 18 19 namespace { 20 21 const uint8 kNoState = 0; 22 // Set on a referenced HpackEntry which is part of the current header set. 23 const uint8 kReferencedImplicitOn = 1; 24 // Set on a referenced HpackEntry which is not part of the current header set. 25 const uint8 kReferencedExplicitOff = 2; 26 // Set on a entries added to the reference set during this encoding. 27 const uint8 kReferencedThisEncoding = 3; 28 29 } // namespace 30 31 HpackEncoder::HpackEncoder(const HpackHuffmanTable& table) 32 : output_stream_(), 33 allow_huffman_compression_(true), 34 huffman_table_(table), 35 char_counts_(NULL), 36 total_char_counts_(NULL) {} 37 38 HpackEncoder::~HpackEncoder() {} 39 40 bool HpackEncoder::EncodeHeaderSet(const std::map<string, string>& header_set, 41 string* output) { 42 // Walk the set of entries to encode, which are not already implied by the 43 // header table's reference set. They must be explicitly emitted. 44 Representations explicit_set(DetermineEncodingDelta(header_set)); 45 for (Representations::const_iterator it = explicit_set.begin(); 46 it != explicit_set.end(); ++it) { 47 // Try to find an exact match. Note that dynamic entries are preferred 48 // by the header table index. 49 HpackEntry* entry = header_table_.GetByNameAndValue(it->first, it->second); 50 if (entry != NULL && !entry->IsStatic()) { 51 // Already in the dynamic table. Simply toggle on. 52 CHECK_EQ(kNoState, entry->state()); 53 EmitDynamicIndex(entry); 54 continue; 55 } 56 57 // Walk the set of entries to be evicted by this insertion. 58 HpackHeaderTable::EntryTable::iterator evict_begin, evict_end, evict_it; 59 header_table_.EvictionSet(it->first, it->second, &evict_begin, &evict_end); 60 61 for (evict_it = evict_begin; evict_it != evict_end; ++evict_it) { 62 HpackEntry* evictee = &(*evict_it); 63 64 if (evict_it->state() == kReferencedImplicitOn) { 65 // Issue twice to explicitly emit. 66 EmitDynamicIndex(evictee); 67 EmitDynamicIndex(evictee); 68 } else if (evictee->state() == kReferencedExplicitOff) { 69 // Eviction saves us from having to explicitly toggle off. 70 evictee->set_state(kNoState); 71 } else if (evictee->state() == kReferencedThisEncoding) { 72 // Already emitted. No action required. 73 evictee->set_state(kNoState); 74 } 75 } 76 if (entry != NULL) { 77 EmitStaticIndex(entry); 78 } else { 79 EmitIndexedLiteral(*it); 80 } 81 } 82 // Walk the reference set, toggling off as needed and clearing encoding state. 83 for (HpackHeaderTable::OrderedEntrySet::const_iterator it = 84 header_table_.reference_set().begin(); 85 it != header_table_.reference_set().end();) { 86 HpackEntry* entry = *(it++); // Step to prevent invalidation. 87 CHECK_NE(kNoState, entry->state()); 88 89 if (entry->state() == kReferencedExplicitOff) { 90 // Explicitly toggle off. 91 EmitDynamicIndex(entry); 92 } 93 entry->set_state(kNoState); 94 } 95 output_stream_.TakeString(output); 96 return true; 97 } 98 99 bool HpackEncoder::EncodeHeaderSetWithoutCompression( 100 const std::map<string, string>& header_set, 101 string* output) { 102 103 allow_huffman_compression_ = false; 104 for (std::map<string, string>::const_iterator it = header_set.begin(); 105 it != header_set.end(); ++it) { 106 // Note that cookies are not crumbled in this case. 107 EmitNonIndexedLiteral(*it); 108 } 109 allow_huffman_compression_ = true; 110 output_stream_.TakeString(output); 111 return true; 112 } 113 114 void HpackEncoder::EmitDynamicIndex(HpackEntry* entry) { 115 DCHECK(!entry->IsStatic()); 116 output_stream_.AppendPrefix(kIndexedOpcode); 117 output_stream_.AppendUint32(header_table_.IndexOf(entry)); 118 119 entry->set_state(kNoState); 120 if (header_table_.Toggle(entry)) { 121 // Was added to the reference set. 122 entry->set_state(kReferencedThisEncoding); 123 } 124 } 125 126 void HpackEncoder::EmitStaticIndex(HpackEntry* entry) { 127 DCHECK(entry->IsStatic()); 128 output_stream_.AppendPrefix(kIndexedOpcode); 129 output_stream_.AppendUint32(header_table_.IndexOf(entry)); 130 131 HpackEntry* new_entry = header_table_.TryAddEntry(entry->name(), 132 entry->value()); 133 if (new_entry) { 134 // This is a static entry: no need to pin. 135 header_table_.Toggle(new_entry); 136 new_entry->set_state(kReferencedThisEncoding); 137 } 138 } 139 140 void HpackEncoder::EmitIndexedLiteral(const Representation& representation) { 141 output_stream_.AppendPrefix(kLiteralIncrementalIndexOpcode); 142 EmitLiteral(representation); 143 144 HpackEntry* new_entry = header_table_.TryAddEntry(representation.first, 145 representation.second); 146 if (new_entry) { 147 header_table_.Toggle(new_entry); 148 new_entry->set_state(kReferencedThisEncoding); 149 } 150 } 151 152 void HpackEncoder::EmitNonIndexedLiteral( 153 const Representation& representation) { 154 output_stream_.AppendPrefix(kLiteralNoIndexOpcode); 155 output_stream_.AppendUint32(0); 156 EmitString(representation.first); 157 EmitString(representation.second); 158 } 159 160 void HpackEncoder::EmitLiteral(const Representation& representation) { 161 const HpackEntry* name_entry = header_table_.GetByName(representation.first); 162 if (name_entry != NULL) { 163 output_stream_.AppendUint32(header_table_.IndexOf(name_entry)); 164 } else { 165 output_stream_.AppendUint32(0); 166 EmitString(representation.first); 167 } 168 EmitString(representation.second); 169 } 170 171 void HpackEncoder::EmitString(StringPiece str) { 172 size_t encoded_size = (!allow_huffman_compression_ ? str.size() 173 : huffman_table_.EncodedSize(str)); 174 if (encoded_size < str.size()) { 175 output_stream_.AppendPrefix(kStringLiteralHuffmanEncoded); 176 output_stream_.AppendUint32(encoded_size); 177 huffman_table_.EncodeString(str, &output_stream_); 178 } else { 179 output_stream_.AppendPrefix(kStringLiteralIdentityEncoded); 180 output_stream_.AppendUint32(str.size()); 181 output_stream_.AppendBytes(str); 182 } 183 UpdateCharacterCounts(str); 184 } 185 186 // static 187 HpackEncoder::Representations HpackEncoder::DetermineEncodingDelta( 188 const std::map<string, string>& header_set) { 189 // Flatten & crumble headers into an ordered list of representations. 190 Representations full_set; 191 for (std::map<string, string>::const_iterator it = header_set.begin(); 192 it != header_set.end(); ++it) { 193 if (it->first == "cookie") { 194 // |CookieToCrumbs()| produces ordered crumbs. 195 CookieToCrumbs(*it, &full_set); 196 } else { 197 // Note std::map guarantees representations are ordered. 198 full_set.push_back(make_pair( 199 StringPiece(it->first), StringPiece(it->second))); 200 } 201 } 202 // Perform a linear merge of ordered representations with the (also ordered) 203 // reference set. Mark each referenced entry with current membership state, 204 // and gather representations which must be explicitly emitted. 205 Representations::const_iterator r_it = full_set.begin(); 206 HpackHeaderTable::OrderedEntrySet::const_iterator s_it = 207 header_table_.reference_set().begin(); 208 209 Representations explicit_set; 210 while (r_it != full_set.end() && 211 s_it != header_table_.reference_set().end()) { 212 // Compare on name, then value. 213 int result = r_it->first.compare((*s_it)->name()); 214 if (result == 0) { 215 result = r_it->second.compare((*s_it)->value()); 216 } 217 218 if (result < 0) { 219 explicit_set.push_back(*r_it); 220 ++r_it; 221 } else if (result > 0) { 222 (*s_it)->set_state(kReferencedExplicitOff); 223 ++s_it; 224 } else { 225 (*s_it)->set_state(kReferencedImplicitOn); 226 ++s_it; 227 ++r_it; 228 } 229 } 230 while (r_it != full_set.end()) { 231 explicit_set.push_back(*r_it); 232 ++r_it; 233 } 234 while (s_it != header_table_.reference_set().end()) { 235 (*s_it)->set_state(kReferencedExplicitOff); 236 ++s_it; 237 } 238 return explicit_set; 239 } 240 241 void HpackEncoder::SetCharCountsStorage(std::vector<size_t>* char_counts, 242 size_t* total_char_counts) { 243 CHECK_LE(256u, char_counts->size()); 244 char_counts_ = char_counts; 245 total_char_counts_ = total_char_counts; 246 } 247 248 void HpackEncoder::UpdateCharacterCounts(base::StringPiece str) { 249 if (char_counts_ == NULL || total_char_counts_ == NULL) { 250 return; 251 } 252 for (StringPiece::const_iterator it = str.begin(); it != str.end(); ++it) { 253 ++(*char_counts_)[static_cast<uint8>(*it)]; 254 } 255 (*total_char_counts_) += str.size(); 256 } 257 258 // static 259 void HpackEncoder::CookieToCrumbs(const Representation& cookie, 260 Representations* out) { 261 size_t prior_size = out->size(); 262 263 // See Section 8.1.3.4 "Compressing the Cookie Header Field" in the HTTP/2 264 // specification at http://tools.ietf.org/html/draft-ietf-httpbis-http2-11 265 // Cookie values are split into individually-encoded HPACK representations. 266 for (size_t pos = 0;;) { 267 size_t end = cookie.second.find(";", pos); 268 269 if (end == StringPiece::npos) { 270 out->push_back(make_pair( 271 cookie.first, 272 cookie.second.substr(pos))); 273 break; 274 } 275 out->push_back(make_pair( 276 cookie.first, 277 cookie.second.substr(pos, end - pos))); 278 279 // Consume next space if present. 280 pos = end + 1; 281 if (pos != cookie.second.size() && cookie.second[pos] == ' ') { 282 pos++; 283 } 284 } 285 // Sort crumbs and remove duplicates. 286 std::sort(out->begin() + prior_size, out->end()); 287 out->erase(std::unique(out->begin() + prior_size, out->end()), 288 out->end()); 289 } 290 291 } // namespace net 292