1 /*
2  * Copyright (c) Facebook, Inc. and its affiliates.
3  * All rights reserved.
4  *
5  * This source code is licensed under the BSD-style license found in the
6  * LICENSE file in the root directory of this source tree.
7  */
8 
9 #include <proxygen/lib/http/codec/SPDYCodec.h>
10 
11 #include <algorithm>
12 #include <boost/algorithm/string.hpp>
13 #include <folly/Conv.h>
14 #include <folly/Memory.h>
15 #include <folly/String.h>
16 #include <folly/io/Cursor.h>
17 #include <glog/logging.h>
18 #include <proxygen/lib/http/HTTPHeaderSize.h>
19 #include <proxygen/lib/http/HTTPMessage.h>
20 #include <proxygen/lib/http/codec/CodecDictionaries.h>
21 #include <proxygen/lib/http/codec/CodecUtil.h>
22 #include <proxygen/lib/http/codec/compress/GzipHeaderCodec.h>
23 #include <proxygen/lib/utils/ParseURL.h>
24 #include <proxygen/lib/utils/UtilInl.h>
25 #include <vector>
26 
27 using folly::IOBuf;
28 using folly::IOBufQueue;
29 using folly::io::Cursor;
30 using folly::io::QueueAppender;
31 using folly::io::RWPrivateCursor;
32 using proxygen::compress::Header;
33 using proxygen::compress::HeaderPieceList;
34 using std::string;
35 using std::unique_ptr;
36 using std::vector;
37 
38 namespace proxygen {
39 
40 namespace {
41 
42 // Sizes, in bytes, of various types and parts of SPDY frames
43 const size_t kFrameSizeDataCommon = 8;    // common prefix of all data frames
44 const size_t kFrameSizeControlCommon = 8; // common prefix of all ctrl frames
45 const size_t kFrameSizeSynStream = 10;    // SYN_STREAM
46 const size_t kFrameSizeSynReplyv3 = 4;    // SPDYv3's SYN_REPLY is shorter
47 const size_t kFrameSizeRstStream = 8;     // RST_STREAM
48 const size_t kFrameSizeGoawayv3 = 8;      // GOAWAY, SPDYv3
49 const size_t kFrameSizeHeaders = 4;       // HEADERS
50 const size_t kFrameSizePing = 4;          // PING
51 const size_t kFrameSizeSettings = 4;      // SETTINGS
52 const size_t kFrameSizeSettingsEntry = 8; // Each entry in SETTINGS
53 const size_t kFrameSizeWindowUpdate = 8;  // WINDOW_UPDATE
54                                           // name/value pair
55 const size_t kFrameSizeNameValuev3 = 4;   // The size in bytes of a
56                                           // name/value pair
57 const size_t kPriShiftv3 = 5;             // How many bits to shift pri, v3
58 
59 const size_t kMaxUncompressed = 96 * 1024; // 96kb ought be enough for anyone
60 
61 #define CTRL_MASK 0x80
62 #define FLAGS_MASK 0xff000000
63 #define STREAM_ID_MASK 0x7fffffff
64 #define VERSION_MASK 0x7fff
65 #define DELTA_WINDOW_SIZE_MASK 0x7fffffff
66 
67 /* The number of bytes of the frame header. */
68 #define FRAME_HEADER_LEN 8
69 
70 // SPDY flags
71 const uint8_t kFlagFin = 0x01;
72 
73 const HTTPCodec::StreamID kMaxStreamID = (1u << 31) - 1;
74 const HTTPCodec::StreamID kVirtualPriorityStreamID = kMaxStreamID + 1;
75 
76 /**
77  * Convenience function to pack SPDY's 8-bit flags field and
78  * 24-bit length field into a single uint32_t so we can write
79  * them out more easily.  (This function packs the flags into
80  * the high order 8 bits of a 32-bit int; because SPDY uses
81  * network byte ordering for these fields, the flag thus ends
82  * up in the right place - in front of the length - when we
83  * serialize the returned uint32_t.)
84  */
flagsAndLength(uint8_t flags,uint32_t length)85 uint32_t flagsAndLength(uint8_t flags, uint32_t length) {
86   length &= 0x00ffffff;
87   length |= (int32_t(flags) << 24);
88   return length;
89 }
90 
appendUint32(uint8_t * & dst,size_t value)91 void appendUint32(uint8_t*& dst, size_t value) {
92   *(uint32_t*)dst = htonl(uint32_t(value));
93   dst += 4;
94 }
95 
parseUint32(Cursor * cursor)96 uint32_t parseUint32(Cursor* cursor) {
97   auto chunk = cursor->peek();
98   if (LIKELY(chunk.second >= sizeof(uint32_t))) {
99     cursor->skip(sizeof(uint32_t));
100     return ntohl(*(uint32_t*)chunk.first);
101   }
102   return cursor->readBE<uint32_t>();
103 }
104 
105 class SPDYSessionFailed : public std::exception {
106  public:
SPDYSessionFailed(spdy::GoawayStatusCode inStatus)107   explicit SPDYSessionFailed(spdy::GoawayStatusCode inStatus)
108       : statusCode(inStatus) {
109   }
110 
111   spdy::GoawayStatusCode statusCode;
112 };
113 
114 class SPDYStreamFailed : public std::exception {
115  public:
SPDYStreamFailed(bool inIsNew,uint32_t inStreamID,uint32_t inStatus,const std::string & inMsg=empty_string)116   SPDYStreamFailed(bool inIsNew,
117                    uint32_t inStreamID,
118                    uint32_t inStatus,
119                    const std::string& inMsg = empty_string)
120       : isNew(inIsNew), streamID(inStreamID), statusCode(inStatus) {
121     message = folly::to<std::string>("new=",
122                                      isNew,
123                                      " streamID=",
124                                      streamID,
125                                      " statusCode=",
126                                      statusCode,
127                                      " message=",
128                                      inMsg);
129   }
130 
~SPDYStreamFailed()131   ~SPDYStreamFailed() throw() override {
132   }
133 
what() const134   const char* what() const throw() override {
135     return message.c_str();
136   }
137 
138   bool isNew;
139   uint32_t streamID;
140   uint32_t statusCode;
141   std::string message;
142 };
143 
144 } // namespace
145 
getVersionSettings(SPDYVersion version)146 const SPDYVersionSettings& SPDYCodec::getVersionSettings(SPDYVersion version) {
147   // XXX: We new and leak the static here intentionally so it doesn't get
148   // destroyed during a call to exit() when threads are still processing
149   // requests resulting in spurious shutdown crashes.
150 
151   // Indexed by SPDYVersion
152   static const auto spdyVersions = new std::vector<SPDYVersionSettings>{
153       // SPDY2 no longer supported; should it ever be added back the lines in
154       // which
155       // this codec creates compress/Header objects need to be updated as SPDY2
156       // constant header names are different from the set of common header
157       // names.
158       // SPDY3
159       {spdy::kNameVersionv3,
160        spdy::kNameStatusv3,
161        spdy::kNameMethodv3,
162        spdy::kNamePathv3,
163        spdy::kNameSchemev3,
164        spdy::kNameHostv3,
165        spdy::kSessionProtoNameSPDY3,
166        parseUint32,
167        appendUint32,
168        (const unsigned char*)kSPDYv3Dictionary,
169        sizeof(kSPDYv3Dictionary),
170        0x8003,
171        kFrameSizeSynReplyv3,
172        kFrameSizeNameValuev3,
173        kFrameSizeGoawayv3,
174        kPriShiftv3,
175        3,
176        0,
177        SPDYVersion::SPDY3,
178        spdy::kVersionStrv3},
179       // SPDY3.1
180       {spdy::kNameVersionv3,
181        spdy::kNameStatusv3,
182        spdy::kNameMethodv3,
183        spdy::kNamePathv3,
184        spdy::kNameSchemev3,
185        spdy::kNameHostv3,
186        spdy::kSessionProtoNameSPDY3,
187        parseUint32,
188        appendUint32,
189        (const unsigned char*)kSPDYv3Dictionary,
190        sizeof(kSPDYv3Dictionary),
191        0x8003,
192        kFrameSizeSynReplyv3,
193        kFrameSizeNameValuev3,
194        kFrameSizeGoawayv3,
195        kPriShiftv3,
196        3,
197        1,
198        SPDYVersion::SPDY3_1,
199        spdy::kVersionStrv31}};
200   auto intVersion = static_cast<unsigned>(version);
201   CHECK_LT(intVersion, spdyVersions->size());
202   return (*spdyVersions)[intVersion];
203 }
204 
SPDYCodec(TransportDirection direction,SPDYVersion version,int spdyCompressionLevel)205 SPDYCodec::SPDYCodec(TransportDirection direction,
206                      SPDYVersion version,
207                      int spdyCompressionLevel /* = Z_NO_COMPRESSION */)
208     : HTTPParallelCodec(direction),
209       versionSettings_(getVersionSettings(version)),
210       frameState_(FrameState::FRAME_HEADER),
211       ctrl_(false),
212       headerCodec_(spdyCompressionLevel, versionSettings_) {
213   VLOG(4) << "creating SPDY/" << static_cast<int>(versionSettings_.majorVersion)
214           << "." << static_cast<int>(versionSettings_.minorVersion) << " codec";
215 
216   // Limit uncompressed headers to 128kb
217   headerCodec_.setMaxUncompressed(kMaxUncompressed);
218   nextEgressPingID_ = nextEgressStreamID_;
219 }
220 
~SPDYCodec()221 SPDYCodec::~SPDYCodec() {
222 }
223 
setMaxFrameLength(uint32_t maxFrameLength)224 void SPDYCodec::setMaxFrameLength(uint32_t maxFrameLength) {
225   maxFrameLength_ = maxFrameLength;
226 }
227 
setMaxUncompressedHeaders(uint32_t maxUncompressed)228 void SPDYCodec::setMaxUncompressedHeaders(uint32_t maxUncompressed) {
229   headerCodec_.setMaxUncompressed(maxUncompressed);
230 }
231 
mapPriorityToDependency(uint8_t priority) const232 HTTPCodec::StreamID SPDYCodec::mapPriorityToDependency(uint8_t priority) const {
233   return kVirtualPriorityStreamID + priority;
234 }
235 
mapDependencyToPriority(StreamID parent) const236 int8_t SPDYCodec::mapDependencyToPriority(StreamID parent) const {
237   if (parent >= kVirtualPriorityStreamID) {
238     return parent - kVirtualPriorityStreamID;
239   }
240   return -1;
241 }
242 
getProtocol() const243 CodecProtocol SPDYCodec::getProtocol() const {
244   switch (versionSettings_.version) {
245     case SPDYVersion::SPDY3:
246       return CodecProtocol::SPDY_3;
247     case SPDYVersion::SPDY3_1:
248       return CodecProtocol::SPDY_3_1;
249   };
250   LOG(FATAL) << "unreachable";
251   return CodecProtocol::SPDY_3_1;
252 }
253 
getUserAgent() const254 const std::string& SPDYCodec::getUserAgent() const {
255   return userAgent_;
256 }
257 
supportsStreamFlowControl() const258 bool SPDYCodec::supportsStreamFlowControl() const {
259   return versionSettings_.majorVersion > 2;
260 }
261 
supportsSessionFlowControl() const262 bool SPDYCodec::supportsSessionFlowControl() const {
263   return versionSettings_.majorVersion > 3 ||
264          (versionSettings_.majorVersion == 3 &&
265           versionSettings_.minorVersion > 0);
266 }
267 
checkLength(uint32_t expectedLength,const std::string & msg)268 void SPDYCodec::checkLength(uint32_t expectedLength, const std::string& msg) {
269   if (length_ != expectedLength) {
270     LOG_IF(ERROR, length_ == 4 && msg != "GOAWAY")
271         << msg << ": invalid length " << length_ << " != " << expectedLength;
272     throw SPDYSessionFailed(spdy::GOAWAY_PROTOCOL_ERROR);
273   }
274 }
275 
checkMinLength(uint32_t minLength,const std::string & msg)276 void SPDYCodec::checkMinLength(uint32_t minLength, const std::string& msg) {
277   if (length_ < minLength) {
278     LOG(ERROR) << msg << ": invalid length " << length_ << " < " << minLength;
279     throw SPDYSessionFailed(spdy::GOAWAY_PROTOCOL_ERROR);
280   }
281 }
282 
onIngress(const folly::IOBuf & buf)283 size_t SPDYCodec::onIngress(const folly::IOBuf& buf) {
284   size_t bytesParsed = 0;
285   currentIngressBuf_ = &buf;
286   try {
287     bytesParsed = parseIngress(buf);
288   } catch (const SPDYSessionFailed& ex) {
289     failSession(ex.statusCode);
290     bytesParsed = buf.computeChainDataLength();
291   }
292   return bytesParsed;
293 }
294 
parseIngress(const folly::IOBuf & buf)295 size_t SPDYCodec::parseIngress(const folly::IOBuf& buf) {
296   const size_t chainLength = buf.computeChainDataLength();
297   Cursor cursor(&buf);
298   size_t avail = cursor.totalLength();
299 
300   // This can parse beyond the current IOBuf
301   for (; avail > 0; avail = cursor.totalLength()) {
302     if (frameState_ == FrameState::FRAME_HEADER) {
303       if (avail < FRAME_HEADER_LEN) {
304         // Make the caller buffer until we get a full frame header
305         break;
306       }
307       auto data = cursor.peek();
308       ctrl_ = (data.first[0] & CTRL_MASK);
309       if (ctrl_) {
310         version_ = cursor.readBE<uint16_t>() & VERSION_MASK;
311         type_ = cursor.readBE<uint16_t>();
312         if (version_ != versionSettings_.majorVersion) {
313           LOG(ERROR) << "Invalid version=" << version_;
314           throw SPDYSessionFailed(spdy::GOAWAY_PROTOCOL_ERROR);
315         }
316       } else {
317         streamId_ = cursor.readBE<uint32_t>() & STREAM_ID_MASK;
318       }
319       length_ = cursor.readBE<uint32_t>();
320       flags_ = (length_ & FLAGS_MASK) >> 24;
321       length_ &= ~FLAGS_MASK;
322       if (ctrl_) {
323         if (length_ > maxFrameLength_) {
324           if (type_ == spdy::SYN_STREAM || type_ == spdy::SYN_REPLY ||
325               type_ == spdy::HEADERS) {
326             uint32_t stream_id = cursor.readBE<uint32_t>() & STREAM_ID_MASK;
327             failStream(true, stream_id, spdy::RST_FRAME_TOO_LARGE);
328             // Compression/stream state is out of sync now
329           }
330           // Since maxFrameLength_ must be at least 8kb and most control frames
331           // have fixed size, only an invalid settings or credential frame can
332           // land here. For invalid credential frames we must send a goaway,
333           // and a settings frame would have > 1023 pairs, of which none are
334           // allowed to be duplicates. Just fail everything.
335           LOG(ERROR) << "excessive frame size length_=" << length_;
336           throw SPDYSessionFailed(spdy::GOAWAY_PROTOCOL_ERROR);
337         }
338         frameState_ = FrameState::CTRL_FRAME_DATA;
339         callback_->onFrameHeader(0, flags_, length_, type_, version_);
340       } else {
341         frameState_ = FrameState::DATA_FRAME_DATA;
342         callback_->onFrameHeader(streamId_, flags_, length_, type_);
343       }
344     } else if (frameState_ == FrameState::CTRL_FRAME_DATA) {
345       if (avail < length_) {
346         // Make the caller buffer the rest of the control frame.
347         // We could attempt to decompress incomplete name/value blocks,
348         // but for now we're favoring simplicity.
349         VLOG(6) << "Need more data: length_=" << length_ << " avail=" << avail;
350         break;
351       }
352       try {
353         onControlFrame(cursor);
354       } catch (const SPDYStreamFailed& ex) {
355         failStream(ex.isNew, ex.streamID, ex.statusCode, ex.what());
356       }
357       frameState_ = FrameState::FRAME_HEADER;
358     } else if (avail > 0 || length_ == 0) {
359       // Data frame data.  Pass everything we have up to the frame boundary
360       DCHECK(FrameState::DATA_FRAME_DATA == frameState_);
361 
362       uint32_t toClone = (avail > std::numeric_limits<uint32_t>::max())
363                              ? std::numeric_limits<uint32_t>::max()
364                              : static_cast<uint32_t>(avail);
365       toClone = std::min(toClone, length_);
366       std::unique_ptr<IOBuf> chunk;
367       cursor.clone(chunk, toClone);
368       deliverCallbackIfAllowed(&HTTPCodec::Callback::onBody,
369                                "onBody",
370                                streamId_,
371                                std::move(chunk),
372                                0);
373       length_ -= toClone;
374     }
375 
376     // Fin handling
377     if (length_ == 0) {
378       if (flags_ & spdy::CTRL_FLAG_FIN) {
379         deliverCallbackIfAllowed(&HTTPCodec::Callback::onMessageComplete,
380                                  "onMessageComplete",
381                                  streamId_,
382                                  false);
383       }
384       frameState_ = FrameState::FRAME_HEADER;
385     }
386   }
387   return chainLength - avail;
388 }
389 
onControlFrame(Cursor & cursor)390 void SPDYCodec::onControlFrame(Cursor& cursor) {
391   switch (type_) {
392     case spdy::SYN_STREAM: {
393       checkMinLength(kFrameSizeSynStream, "SYN_STREAM");
394       streamId_ = cursor.readBE<uint32_t>() & STREAM_ID_MASK;
395       uint32_t assocStream = cursor.readBE<uint32_t>();
396       uint8_t pri = cursor.read<uint8_t>() >> versionSettings_.priShift;
397       uint8_t slot = cursor.read<uint8_t>();
398       length_ -= kFrameSizeSynStream;
399       auto result = decodeHeaders(cursor);
400       checkLength(0, "SYN_STREAM");
401       onSynStream(assocStream,
402                   pri,
403                   slot,
404                   result.headers,
405                   headerCodec_.getDecodedSize());
406       break;
407     }
408     case spdy::SYN_REPLY: {
409       checkMinLength(versionSettings_.synReplySize, "SYN_REPLY");
410       streamId_ = cursor.readBE<uint32_t>() & STREAM_ID_MASK;
411       length_ -= versionSettings_.synReplySize;
412       if (version_ == 2) {
413         // 2 byte unused
414         cursor.skip(2);
415       }
416       auto result = decodeHeaders(cursor);
417       checkLength(0, "SYN_REPLY");
418       onSynReply(result.headers, headerCodec_.getDecodedSize());
419       break;
420     }
421     case spdy::RST_STREAM: {
422       checkLength(kFrameSizeRstStream, "RST");
423       streamId_ = cursor.readBE<uint32_t>() & STREAM_ID_MASK;
424       uint32_t statusCode = cursor.readBE<uint32_t>();
425       onRstStream(statusCode);
426       break;
427     }
428     case spdy::SETTINGS: {
429       checkMinLength(kFrameSizeSettings, "SETTINGS");
430       uint32_t numSettings = cursor.readBE<uint32_t>();
431       length_ -= sizeof(uint32_t);
432       if (length_ / 8 < numSettings) {
433         LOG(ERROR) << "SETTINGS: number of settings to high. " << length_
434                    << " < 8 * " << numSettings;
435         throw SPDYSessionFailed(spdy::GOAWAY_PROTOCOL_ERROR);
436       }
437       SettingList settings;
438       for (uint32_t i = 0; i < numSettings; i++) {
439         uint32_t id = 0;
440         if (version_ == 2) {
441           id = cursor.readLE<uint32_t>();
442         } else {
443           id = cursor.readBE<uint32_t>();
444         }
445         uint32_t value = cursor.readBE<uint32_t>();
446         uint8_t flags = (id & FLAGS_MASK) >> 24;
447         id &= ~FLAGS_MASK;
448         settings.emplace_back(flags, id, value);
449       }
450       onSettings(settings);
451       break;
452     }
453     case spdy::NOOP:
454       VLOG(4) << "Noop received. Doing nothing.";
455       checkLength(0, "NOOP");
456       break;
457     case spdy::PING: {
458       checkLength(kFrameSizePing, "PING");
459       uint32_t unique_id = cursor.readBE<uint32_t>();
460       onPing(unique_id);
461       break;
462     }
463     case spdy::GOAWAY: {
464       checkLength(versionSettings_.goawaySize, "GOAWAY");
465       uint32_t lastStream = cursor.readBE<uint32_t>();
466       uint32_t statusCode = 0;
467       if (version_ == 3) {
468         statusCode = cursor.readBE<uint32_t>();
469       }
470       onGoaway(lastStream, statusCode);
471       break;
472     }
473     case spdy::HEADERS: {
474       // Note: this is for the HEADERS frame type, not the initial headers
475       checkMinLength(kFrameSizeHeaders, "HEADERS");
476       streamId_ = cursor.readBE<uint32_t>() & STREAM_ID_MASK;
477       length_ -= kFrameSizeHeaders;
478       if (version_ == 2) {
479         // 2 byte unused
480         cursor.skip(2);
481         length_ -= 2;
482       }
483       auto result = decodeHeaders(cursor);
484       checkLength(0, "HEADERS");
485       onHeaders(result.headers);
486       break;
487     }
488     case spdy::WINDOW_UPDATE: {
489       checkLength(kFrameSizeWindowUpdate, "WINDOW_UPDATE");
490       streamId_ = cursor.readBE<uint32_t>() & STREAM_ID_MASK;
491       uint32_t delta = cursor.readBE<uint32_t>() & DELTA_WINDOW_SIZE_MASK;
492       onWindowUpdate(delta);
493       break;
494     }
495     case spdy::CREDENTIAL: {
496       VLOG(4) << "Skipping unsupported/deprecated CREDENTIAL frame";
497       // Fall through to default case
498     }
499     default:
500       VLOG(3) << "unimplemented control frame type " << type_
501               << ", frame length: " << length_;
502       // From spdy spec:
503       // Control frame processing requirements:
504       // If an endpoint receives a control frame for a type it does not
505       // recognize, it must ignore the frame.
506 
507       // Consume rest of the frame to skip processing it further
508       cursor.skip(length_);
509       length_ = 0;
510       return;
511   }
512 }
513 
decodeHeaders(Cursor & cursor)514 HeaderDecodeResult SPDYCodec::decodeHeaders(Cursor& cursor) {
515   auto result = headerCodec_.decode(cursor, length_);
516   if (result.hasError()) {
517     auto err = result.error();
518     if (err == GzipDecodeError::HEADERS_TOO_LARGE ||
519         err == GzipDecodeError::INFLATE_DICTIONARY ||
520         err == GzipDecodeError::BAD_ENCODING) {
521       // Fail stream only for FRAME_TOO_LARGE error
522       if (err == GzipDecodeError::HEADERS_TOO_LARGE) {
523         failStream(true, streamId_, spdy::RST_FRAME_TOO_LARGE);
524       }
525       throw SPDYSessionFailed(spdy::GOAWAY_PROTOCOL_ERROR);
526     }
527     // For other types of errors we throw a stream error
528     bool newStream = (type_ != spdy::HEADERS);
529     throw SPDYStreamFailed(newStream,
530                            streamId_,
531                            spdy::RST_PROTOCOL_ERROR,
532                            "Error parsing header: " + folly::to<string>(err));
533   }
534 
535   length_ -= result->bytesConsumed;
536   return *result;
537 }
538 
isSPDYReserved(const std::string & name)539 bool SPDYCodec::isSPDYReserved(const std::string& name) {
540   return (versionSettings_.majorVersion == 2 &&
541           ((transportDirection_ == TransportDirection::DOWNSTREAM &&
542             (caseInsensitiveEqual(name, spdy::kNameStatusv2) ||
543              caseInsensitiveEqual(name, spdy::kNameVersionv2))) ||
544            (transportDirection_ == TransportDirection::UPSTREAM &&
545             (caseInsensitiveEqual(name, spdy::kNameMethodv2) ||
546              caseInsensitiveEqual(name, spdy::kNameSchemev2) ||
547              caseInsensitiveEqual(name, spdy::kNamePathv2) ||
548              caseInsensitiveEqual(name, spdy::kNameVersionv2)))));
549 }
550 
551 // Add the SPDY-specific header fields that hold the
552 // equivalent of the HTTP/1.x request-line or status-line.
encodeHeaders(const HTTPMessage & msg,vector<Header> & allHeaders,uint32_t headroom,HTTPHeaderSize * size,const folly::Optional<HTTPHeaders> & extraHeaders)553 unique_ptr<IOBuf> SPDYCodec::encodeHeaders(
554     const HTTPMessage& msg,
555     vector<Header>& allHeaders,
556     uint32_t headroom,
557     HTTPHeaderSize* size,
558     const folly::Optional<HTTPHeaders>& extraHeaders) {
559 
560   // We explicitly provide both the code and header name here
561   // as HTTP_HEADER_OTHER does not map to kNameVersionv3 and we don't want a
562   // perf penalty hash kNameVersionv3 to HTTP_HEADER_OTHER
563   allHeaders.emplace_back(
564       HTTP_HEADER_OTHER, versionSettings_.versionStr, spdy::httpVersion);
565 
566   // Add the HTTP headers supplied by the caller, but skip
567   // any per-hop headers that aren't supported in SPDY.
568   auto headerEncodeHelper =
569       [&](HTTPHeaderCode code, const string& name, const string& value) {
570         static const std::bitset<256> s_perHopHeaderCodes{[] {
571           std::bitset<256> bs;
572           // SPDY per-hop headers
573           bs[HTTP_HEADER_CONNECTION] = true;
574           bs[HTTP_HEADER_HOST] = true;
575           bs[HTTP_HEADER_KEEP_ALIVE] = true;
576           bs[HTTP_HEADER_PROXY_CONNECTION] = true;
577           bs[HTTP_HEADER_TRANSFER_ENCODING] = true;
578           bs[HTTP_HEADER_UPGRADE] = true;
579           return bs;
580         }()};
581 
582         if (s_perHopHeaderCodes[code] || isSPDYReserved(name)) {
583           VLOG(3) << "Dropping SPDY reserved header " << name;
584           return;
585         }
586         if (name.length() == 0) {
587           VLOG(2) << "Dropping header with empty name";
588           return;
589         }
590         if (versionSettings_.majorVersion == 2 && value.length() == 0) {
591           VLOG(2) << "Dropping header \"" << name
592                   << "\" with empty value for spdy/2";
593           return;
594         }
595         allHeaders.emplace_back(code, name, value);
596       };
597   msg.getHeaders().forEachWithCode(headerEncodeHelper);
598   if (extraHeaders) {
599     extraHeaders->forEachWithCode(headerEncodeHelper);
600   }
601 
602   headerCodec_.setEncodeHeadroom(headroom);
603   auto out = headerCodec_.encode(allHeaders);
604   if (size) {
605     *size = headerCodec_.getEncodedSize();
606   }
607 
608   return out;
609 }
610 
serializeResponseHeaders(const HTTPMessage & msg,uint32_t headroom,HTTPHeaderSize * size,const folly::Optional<HTTPHeaders> & extraHeaders)611 unique_ptr<IOBuf> SPDYCodec::serializeResponseHeaders(
612     const HTTPMessage& msg,
613     uint32_t headroom,
614     HTTPHeaderSize* size,
615     const folly::Optional<HTTPHeaders>& extraHeaders) {
616 
617   // Note: the header-sorting code works with pointers to strings.
618   // The role of this local status string is to hold the generated
619   // status code long enough for the sort (done later within the
620   // same scope) to be able to access it.
621   string status;
622 
623   const HTTPHeaders& headers = msg.getHeaders();
624   vector<Header> allHeaders;
625   allHeaders.reserve(headers.size() + 4);
626 
627   if (msg.getStatusMessage().empty()) {
628     status = folly::to<string>(msg.getStatusCode());
629   } else {
630     status =
631         folly::to<string>(msg.getStatusCode(), " ", msg.getStatusMessage());
632   }
633   allHeaders.emplace_back(HTTP_HEADER_COLON_STATUS, status);
634   // See comment above regarding status
635   string date;
636   if (!headers.exists(HTTP_HEADER_DATE)) {
637     date = HTTPMessage::formatDateHeader();
638     allHeaders.emplace_back(HTTP_HEADER_DATE, date);
639   }
640 
641   return encodeHeaders(msg, allHeaders, headroom, size, extraHeaders);
642 }
643 
serializeRequestHeaders(const HTTPMessage & msg,bool isPushed,uint32_t headroom,HTTPHeaderSize * size,const folly::Optional<HTTPHeaders> & extraHeaders)644 unique_ptr<IOBuf> SPDYCodec::serializeRequestHeaders(
645     const HTTPMessage& msg,
646     bool isPushed,
647     uint32_t headroom,
648     HTTPHeaderSize* size,
649     const folly::Optional<HTTPHeaders>& extraHeaders) {
650 
651   const HTTPHeaders& headers = msg.getHeaders();
652   vector<Header> allHeaders;
653   allHeaders.reserve(headers.size() + 6);
654 
655   const string& method = msg.getMethodString();
656   static const string https("https");
657   static const string http("http");
658   const string& scheme = msg.isSecure() ? https : http;
659   string path = msg.getURL();
660 
661   CHECK_GT(versionSettings_.majorVersion, 2) << "SPDY/2 no longer supported";
662 
663   string pushString;
664   if (isPushed) {
665     pushString = msg.getPushStatusStr();
666     allHeaders.emplace_back(HTTP_HEADER_COLON_STATUS, pushString);
667   } else {
668     allHeaders.emplace_back(HTTP_HEADER_COLON_METHOD, method);
669   }
670   allHeaders.emplace_back(HTTP_HEADER_COLON_SCHEME, scheme);
671   allHeaders.emplace_back(HTTP_HEADER_COLON_PATH, path);
672   if (versionSettings_.majorVersion == 3) {
673     DCHECK(headers.exists(HTTP_HEADER_HOST));
674     const string& host = headers.getSingleOrEmpty(HTTP_HEADER_HOST);
675     // We explicitly provide both the code and header name here
676     // as HTTP_HEADER_OTHER does not map to kNameHostv3 and we don't want a
677     // perf penalty hash kNameHostv3 to HTTP_HEADER_OTHER
678     allHeaders.emplace_back(HTTP_HEADER_OTHER, versionSettings_.hostStr, host);
679   }
680 
681   return encodeHeaders(msg, allHeaders, headroom, size, extraHeaders);
682 }
683 
generateHeader(folly::IOBufQueue & writeBuf,StreamID stream,const HTTPMessage & msg,bool eom,HTTPHeaderSize * size,const folly::Optional<HTTPHeaders> & extraHeaders)684 void SPDYCodec::generateHeader(
685     folly::IOBufQueue& writeBuf,
686     StreamID stream,
687     const HTTPMessage& msg,
688     bool eom,
689     HTTPHeaderSize* size,
690     const folly::Optional<HTTPHeaders>& extraHeaders) {
691   if (!isStreamIngressEgressAllowed(stream)) {
692     VLOG(2) << "Suppressing SYN_STREAM/REPLY for stream=" << stream
693             << " ingressGoawayAck_=" << ingressGoawayAck_;
694     if (size) {
695       size->compressed = 0;
696       size->uncompressed = 0;
697     }
698     return;
699   }
700   if (transportDirection_ == TransportDirection::UPSTREAM) {
701     generateSynStream(stream, 0, writeBuf, msg, eom, size, extraHeaders);
702   } else {
703     generateSynReply(stream, writeBuf, msg, eom, size, extraHeaders);
704   }
705 }
706 
generatePushPromise(folly::IOBufQueue & writeBuf,StreamID stream,const HTTPMessage & msg,StreamID assocStream,bool eom,HTTPHeaderSize * size)707 void SPDYCodec::generatePushPromise(folly::IOBufQueue& writeBuf,
708                                     StreamID stream,
709                                     const HTTPMessage& msg,
710                                     StreamID assocStream,
711                                     bool eom,
712                                     HTTPHeaderSize* size) {
713   DCHECK(assocStream != NoStream);
714   if (!isStreamIngressEgressAllowed(stream)) {
715     VLOG(2) << "Suppressing SYN_STREAM/REPLY for stream=" << stream
716             << " ingressGoawayAck_=" << ingressGoawayAck_;
717     if (size) {
718       size->compressed = 0;
719       size->uncompressed = 0;
720     }
721     return;
722   }
723   generateSynStream(stream, assocStream, writeBuf, msg, eom, size);
724 }
725 
generateSynStream(StreamID stream,StreamID assocStream,folly::IOBufQueue & writeBuf,const HTTPMessage & msg,bool eom,HTTPHeaderSize * size,const folly::Optional<HTTPHeaders> & extraHeaders)726 void SPDYCodec::generateSynStream(
727     StreamID stream,
728     StreamID assocStream,
729     folly::IOBufQueue& writeBuf,
730     const HTTPMessage& msg,
731     bool eom,
732     HTTPHeaderSize* size,
733     const folly::Optional<HTTPHeaders>& extraHeaders) {
734   // Pushed streams must have an even streamId and an odd assocStream
735   CHECK((assocStream == NoStream && (stream % 2 == 1)) ||
736         ((stream % 2 == 0) && (assocStream % 2 == 1)))
737       << "Invalid stream ids stream=" << stream
738       << " assocStream=" << assocStream;
739 
740   // Serialize the compressed representation of the headers
741   // first because we need to write its length.  The
742   // serializeRequestHeaders() method allocates an IOBuf to
743   // hold the headers, but we can tell it to reserve
744   // enough headroom at the start of the IOBuf to hold
745   // the metadata we'll need to add once we know the
746   // length.
747   uint32_t fieldsSize = kFrameSizeSynStream;
748   uint32_t headroom = kFrameSizeControlCommon + fieldsSize;
749   bool isPushed = (assocStream != NoStream);
750   unique_ptr<IOBuf> out(
751       serializeRequestHeaders(msg, isPushed, headroom, size, extraHeaders));
752 
753   // The length field in the SYN_STREAM header holds the number
754   // of bytes that follow it.  That's the length of the fields
755   // specific to the SYN_STREAM message (all of which come after
756   // the length field) plus the length of the serialized header
757   // name/value block.
758   uint32_t len = fieldsSize + out->computeChainDataLength();
759 
760   // Generate a control frame header of type SYN_STREAM within
761   // the headroom that serializeRequestHeaders() reserved for us
762   // at the start of the IOBuf.
763   uint8_t flags = spdy::CTRL_FLAG_NONE;
764   if (assocStream != NoStream) {
765     flags |= spdy::CTRL_FLAG_UNIDIRECTIONAL;
766   }
767   if (eom) {
768     flags |= spdy::CTRL_FLAG_FIN;
769   }
770   out->prepend(headroom);
771   RWPrivateCursor cursor(out.get());
772   cursor.writeBE(versionSettings_.controlVersion);
773   cursor.writeBE(uint16_t(spdy::SYN_STREAM));
774   cursor.writeBE(flagsAndLength(flags, len));
775   cursor.writeBE(uint32_t(stream));
776   cursor.writeBE(uint32_t(assocStream));
777   // If the message set HTTP/2 priority instead of SPDY priority, we lose
778   // priority information since we can't collapse it.
779   // halve priority for SPDY/2
780   uint8_t pri = msg.getPriority() >> (3 - versionSettings_.majorVersion);
781   cursor.writeBE(uint16_t(pri << (versionSettings_.priShift + 8)));
782 
783   // Now that we have a complete SYN_STREAM control frame, append
784   // it to the writeBuf.
785   writeBuf.append(std::move(out));
786 }
787 
generateSynReply(StreamID stream,folly::IOBufQueue & writeBuf,const HTTPMessage & msg,bool eom,HTTPHeaderSize * size,const folly::Optional<HTTPHeaders> & extraHeaders)788 void SPDYCodec::generateSynReply(
789     StreamID stream,
790     folly::IOBufQueue& writeBuf,
791     const HTTPMessage& msg,
792     bool eom,
793     HTTPHeaderSize* size,
794     const folly::Optional<HTTPHeaders>& extraHeaders) {
795   // Serialize the compressed representation of the headers
796   // first because we need to write its length.  The
797   // serializeResponseHeaders() method allocates an IOBuf to
798   // hold the headers, but we can tell it to reserve
799   // enough headroom at the start of the IOBuf to hold
800   // the metadata we'll need to add once we know the
801   // length.
802   uint32_t headroom = kFrameSizeControlCommon + versionSettings_.synReplySize;
803   unique_ptr<IOBuf> out(
804       serializeResponseHeaders(msg, headroom, size, extraHeaders));
805 
806   // The length field in the SYN_REPLY header holds the number
807   // of bytes that follow it.  That's the length of the fields
808   // specific to the SYN_REPLY message (all of which come after
809   // the length field) plus the length of the serialized header
810   // name/value block.
811   uint32_t len = versionSettings_.synReplySize + out->computeChainDataLength();
812 
813   // Generate a control frame header of type SYN_REPLY within
814   // the headroom that we serializeResponseHeaders() reserved for us
815   // at the start of the IOBuf.1
816   uint8_t flags = eom ? spdy::CTRL_FLAG_FIN : spdy::CTRL_FLAG_NONE;
817   out->prepend(headroom);
818   RWPrivateCursor cursor(out.get());
819   cursor.writeBE(versionSettings_.controlVersion);
820   cursor.writeBE(uint16_t(spdy::SYN_REPLY));
821   cursor.writeBE(flagsAndLength(flags, len));
822   cursor.writeBE(uint32_t(stream)); // TODO: stream should never be bigger than
823                                     // 2^31
824   if (versionSettings_.majorVersion == 2) {
825     cursor.writeBE(uint16_t(0));
826   }
827 
828   // Now that we have a complete SYN_REPLY control frame, append
829   // it to the writeBuf.
830   writeBuf.append(std::move(out));
831 }
832 
generateBody(folly::IOBufQueue & writeBuf,StreamID stream,std::unique_ptr<folly::IOBuf> chain,folly::Optional<uint8_t>,bool eom)833 size_t SPDYCodec::generateBody(folly::IOBufQueue& writeBuf,
834                                StreamID stream,
835                                std::unique_ptr<folly::IOBuf> chain,
836                                folly::Optional<uint8_t> /*padding*/,
837                                bool eom) {
838   if (!isStreamIngressEgressAllowed(stream)) {
839     VLOG(2) << "Suppressing DATA for stream=" << stream
840             << " ingressGoawayAck_=" << ingressGoawayAck_;
841     return 0;
842   }
843   size_t len = chain->computeChainDataLength();
844   if (len == 0) {
845     return len;
846   }
847 
848   // TODO if the data length is 2^24 or greater, split it into
849   // multiple data frames.  Proxygen should never be writing that
850   // much data at once, but other apps that use this codec might.
851   CHECK_LT(len, (1 << 24));
852 
853   uint8_t flags = (eom) ? kFlagFin : 0;
854   generateDataFrame(writeBuf, uint32_t(stream), flags, len, std::move(chain));
855   return len;
856 }
857 
generateChunkHeader(folly::IOBufQueue &,StreamID,size_t)858 size_t SPDYCodec::generateChunkHeader(folly::IOBufQueue& /*writeBuf*/,
859                                       StreamID /*stream*/,
860                                       size_t /*length*/) {
861   // SPDY chunk headers are built into the data frames
862   return 0;
863 }
864 
generateChunkTerminator(folly::IOBufQueue &,StreamID)865 size_t SPDYCodec::generateChunkTerminator(folly::IOBufQueue& /*writeBuf*/,
866                                           StreamID /*stream*/) {
867   // SPDY has no chunk terminator
868   return 0;
869 }
870 
generateTrailers(folly::IOBufQueue &,StreamID,const HTTPHeaders &)871 size_t SPDYCodec::generateTrailers(folly::IOBufQueue& /*writeBuf*/,
872                                    StreamID /*stream*/,
873                                    const HTTPHeaders& /*trailers*/) {
874   // TODO generate a HEADERS frame?  An additional HEADERS frame
875   // somewhere after the SYN_REPLY seems to be the SPDY equivalent
876   // of HTTP/1.1's trailers.
877   return 0;
878 }
879 
generateEOM(folly::IOBufQueue & writeBuf,StreamID stream)880 size_t SPDYCodec::generateEOM(folly::IOBufQueue& writeBuf, StreamID stream) {
881   VLOG(4) << "sending EOM for stream=" << stream;
882   if (!isStreamIngressEgressAllowed(stream)) {
883     VLOG(2) << "Suppressing EOM for stream=" << stream
884             << " ingressGoawayAck_=" << ingressGoawayAck_;
885     return 0;
886   }
887   generateDataFrame(writeBuf, uint32_t(stream), kFlagFin, 0, nullptr);
888   return 8; // size of data frame header
889 }
890 
generateRstStream(IOBufQueue & writeBuf,StreamID stream,ErrorCode code)891 size_t SPDYCodec::generateRstStream(IOBufQueue& writeBuf,
892                                     StreamID stream,
893                                     ErrorCode code) {
894   DCHECK_GT(stream, 0);
895   VLOG(4) << "sending RST_STREAM for stream=" << stream
896           << " with code=" << getErrorCodeString(code);
897 
898   // Suppress any EOM callback for the current frame.
899   if (stream == streamId_) {
900     flags_ &= ~spdy::CTRL_FLAG_FIN;
901   }
902 
903   if (!isStreamIngressEgressAllowed(stream)) {
904     VLOG(2) << "Suppressing RST_STREAM for stream=" << stream
905             << " ingressGoawayAck_=" << ingressGoawayAck_;
906     return 0;
907   }
908 
909   const uint32_t statusCode = (uint32_t)spdy::errorCodeToReset(code);
910   const size_t frameSize = kFrameSizeControlCommon + kFrameSizeRstStream;
911   const size_t expectedLength = writeBuf.chainLength() + frameSize;
912   QueueAppender appender(&writeBuf, frameSize);
913   appender.writeBE(versionSettings_.controlVersion);
914   appender.writeBE(uint16_t(spdy::RST_STREAM));
915   appender.writeBE(flagsAndLength(0, kFrameSizeRstStream));
916   appender.writeBE(uint32_t(stream));
917   appender.writeBE(rstStatusSupported(statusCode)
918                        ? statusCode
919                        : (uint32_t)spdy::RST_PROTOCOL_ERROR);
920   DCHECK_EQ(writeBuf.chainLength(), expectedLength);
921   return frameSize;
922 }
923 
generateGoaway(IOBufQueue & writeBuf,StreamID lastStream,ErrorCode code,std::unique_ptr<folly::IOBuf> debugData)924 size_t SPDYCodec::generateGoaway(IOBufQueue& writeBuf,
925                                  StreamID lastStream,
926                                  ErrorCode code,
927                                  std::unique_ptr<folly::IOBuf> debugData) {
928   const uint32_t statusCode = (uint32_t)spdy::errorCodeToGoaway(code);
929   const size_t frameSize =
930       kFrameSizeControlCommon + (size_t)versionSettings_.goawaySize;
931 
932   if (sessionClosing_ == ClosingState::CLOSING) {
933     VLOG(4) << "Not sending GOAWAY for closed session";
934     return 0;
935   }
936   // If the caller didn't specify a last stream, choose the correct one
937   // If there's an error or this is the final GOAWAY, use last received stream
938   if (lastStream == HTTPCodec::MaxStreamID) {
939     if (code != ErrorCode::NO_ERROR || !isReusable() || isWaitingToDrain()) {
940       lastStream = getLastIncomingStreamID();
941     } else {
942       lastStream = kMaxStreamID;
943     }
944   }
945 
946   DCHECK_LE(lastStream, egressGoawayAck_) << "Cannot increase last good stream";
947   egressGoawayAck_ = lastStream;
948   const size_t expectedLength = writeBuf.chainLength() + frameSize;
949   QueueAppender appender(&writeBuf, frameSize);
950   appender.writeBE(versionSettings_.controlVersion);
951 
952   if (code != ErrorCode::NO_ERROR) {
953     sessionClosing_ = ClosingState::CLOSING;
954   }
955 
956   string debugInfo =
957       (debugData) ? folly::to<string>(" with debug info=",
958                                       std::string((char*)debugData->data(),
959                                                   debugData->length()))
960                   : "";
961   VLOG(4) << "Sending GOAWAY with last acknowledged stream=" << lastStream
962           << " with code=" << getErrorCodeString(code) << debugInfo;
963 
964   appender.writeBE(uint16_t(spdy::GOAWAY));
965   appender.writeBE(flagsAndLength(0, versionSettings_.goawaySize));
966   appender.writeBE(uint32_t(lastStream));
967   if (versionSettings_.majorVersion == 3) {
968     appender.writeBE(statusCode);
969   }
970   switch (sessionClosing_) {
971     case ClosingState::OPEN:
972       sessionClosing_ = ClosingState::CLOSING;
973       break;
974     case ClosingState::OPEN_WITH_GRACEFUL_DRAIN_ENABLED:
975       if (lastStream == kMaxStreamID) {
976         sessionClosing_ = ClosingState::FIRST_GOAWAY_SENT;
977       } else {
978         // The user of this codec decided not to do the double goaway
979         // drain
980         sessionClosing_ = ClosingState::CLOSING;
981       }
982       break;
983     case ClosingState::FIRST_GOAWAY_SENT:
984       sessionClosing_ = ClosingState::CLOSING;
985       break;
986     case ClosingState::CLOSING:
987       break;
988     case ClosingState::CLOSED:
989       LOG(FATAL) << "unreachable";
990       break;
991   }
992   DCHECK_EQ(writeBuf.chainLength(), expectedLength);
993   return frameSize;
994 }
995 
generatePingRequest(IOBufQueue & writeBuf,folly::Optional<uint64_t>)996 size_t SPDYCodec::generatePingRequest(IOBufQueue& writeBuf,
997                                       folly::Optional<uint64_t> /* data */) {
998   const auto id = nextEgressPingID_;
999   nextEgressPingID_ += 2;
1000   VLOG(4) << "Generating ping request with id=" << id;
1001   return generatePingCommon(writeBuf, id);
1002 }
1003 
generatePingReply(IOBufQueue & writeBuf,uint64_t data)1004 size_t SPDYCodec::generatePingReply(IOBufQueue& writeBuf, uint64_t data) {
1005   VLOG(4) << "Generating ping reply with id=" << data;
1006   return generatePingCommon(writeBuf, data);
1007 }
1008 
generatePingCommon(IOBufQueue & writeBuf,uint64_t data)1009 size_t SPDYCodec::generatePingCommon(IOBufQueue& writeBuf, uint64_t data) {
1010   const size_t frameSize = kFrameSizeControlCommon + kFrameSizePing;
1011   const size_t expectedLength = writeBuf.chainLength() + frameSize;
1012   QueueAppender appender(&writeBuf, frameSize);
1013   appender.writeBE(versionSettings_.controlVersion);
1014   appender.writeBE(uint16_t(spdy::PING));
1015   appender.writeBE(flagsAndLength(0, kFrameSizePing));
1016   appender.writeBE(uint32_t(data));
1017   DCHECK_EQ(writeBuf.chainLength(), expectedLength);
1018   return frameSize;
1019 }
1020 
generateSettings(folly::IOBufQueue & writeBuf)1021 size_t SPDYCodec::generateSettings(folly::IOBufQueue& writeBuf) {
1022   auto numSettings = egressSettings_.getNumSettings();
1023   for (const auto& setting : egressSettings_.getAllSettings()) {
1024     if (!spdy::httpToSpdySettingsId(setting.id)) {
1025       numSettings--;
1026     }
1027   }
1028   VLOG(4) << "generating " << (unsigned)numSettings << " settings";
1029   const size_t frameSize = kFrameSizeControlCommon + kFrameSizeSettings +
1030                            (kFrameSizeSettingsEntry * numSettings);
1031   const size_t expectedLength = writeBuf.chainLength() + frameSize;
1032   QueueAppender appender(&writeBuf, frameSize);
1033   appender.writeBE(versionSettings_.controlVersion);
1034   appender.writeBE(uint16_t(spdy::SETTINGS));
1035   appender.writeBE(flagsAndLength(
1036       spdy::FLAG_SETTINGS_CLEAR_SETTINGS,
1037       kFrameSizeSettings + kFrameSizeSettingsEntry * numSettings));
1038   appender.writeBE(uint32_t(numSettings));
1039   for (const auto& setting : egressSettings_.getAllSettings()) {
1040     auto settingId = spdy::httpToSpdySettingsId(setting.id);
1041     if (!settingId) {
1042       LOG(WARNING) << "Invalid SpdySetting " << (uint32_t)setting.id;
1043       continue;
1044     }
1045     VLOG(5) << " writing setting with id=" << *settingId
1046             << ", value=" << setting.value;
1047     if (versionSettings_.majorVersion == 2) {
1048       // ID: 24-bits in little-endian byte order.
1049       // This is inconsistent with other values in SPDY and
1050       // is the result of a bug in the initial v2 implementation.
1051       appender.writeLE(flagsAndLength(0, *settingId));
1052     } else {
1053       appender.writeBE(flagsAndLength(0, *settingId));
1054     }
1055     appender.writeBE<uint32_t>(setting.value);
1056   }
1057   DCHECK_EQ(writeBuf.chainLength(), expectedLength);
1058   return frameSize;
1059 }
1060 
generateWindowUpdate(folly::IOBufQueue & writeBuf,StreamID stream,uint32_t delta)1061 size_t SPDYCodec::generateWindowUpdate(folly::IOBufQueue& writeBuf,
1062                                        StreamID stream,
1063                                        uint32_t delta) {
1064   if (versionSettings_.majorVersion < 3 ||
1065       (stream == NoStream && versionSettings_.majorVersion == 3 &&
1066        versionSettings_.minorVersion == 0)) {
1067     return 0;
1068   }
1069 
1070   if (!isStreamIngressEgressAllowed(stream)) {
1071     VLOG(2) << "Suppressing WINDOW_UPDATE for stream=" << stream
1072             << " ingressGoawayAck_=" << ingressGoawayAck_;
1073     return 0;
1074   }
1075 
1076   VLOG(4) << "generating window update for stream=" << stream << ": Processed "
1077           << delta << " bytes";
1078   const size_t frameSize = kFrameSizeControlCommon + kFrameSizeWindowUpdate;
1079   const size_t expectedLength = writeBuf.chainLength() + frameSize;
1080   QueueAppender appender(&writeBuf, frameSize);
1081   appender.writeBE(versionSettings_.controlVersion);
1082   appender.writeBE(uint16_t(spdy::WINDOW_UPDATE));
1083   appender.writeBE(flagsAndLength(0, kFrameSizeWindowUpdate));
1084   appender.writeBE(uint32_t(stream)); // TODO: ensure stream < 2^31
1085   appender.writeBE(delta); // TODO: delta should never be bigger than 2^31
1086   DCHECK_EQ(writeBuf.chainLength(), expectedLength);
1087   return frameSize;
1088 }
1089 
addPriorityNodes(PriorityQueue & queue,folly::IOBufQueue &,uint8_t)1090 size_t SPDYCodec::addPriorityNodes(PriorityQueue& queue,
1091                                    folly::IOBufQueue&,
1092                                    uint8_t) {
1093   HTTPCodec::StreamID parent = 0;
1094   // For SPDY, we always create 8 virtual nodes regardless of maxLevel
1095   for (uint8_t pri = 0; pri < 8; pri++) {
1096     auto dependency = mapPriorityToDependency(pri);
1097     queue.addPriorityNode(dependency, parent);
1098     parent = dependency;
1099   }
1100   return 0;
1101 }
1102 
getVersion() const1103 uint8_t SPDYCodec::getVersion() const {
1104   return versionSettings_.majorVersion;
1105 }
1106 
getMinorVersion() const1107 uint8_t SPDYCodec::getMinorVersion() const {
1108   return versionSettings_.minorVersion;
1109 }
1110 
generateDataFrame(folly::IOBufQueue & writeBuf,uint32_t streamID,uint8_t flags,uint32_t length,unique_ptr<IOBuf> payload)1111 size_t SPDYCodec::generateDataFrame(folly::IOBufQueue& writeBuf,
1112                                     uint32_t streamID,
1113                                     uint8_t flags,
1114                                     uint32_t length,
1115                                     unique_ptr<IOBuf> payload) {
1116   const size_t frameSize = kFrameSizeDataCommon;
1117   uint64_t payloadLength = 0;
1118   if (payload && !payload->isSharedOne() && payload->headroom() >= frameSize &&
1119       writeBuf.tailroom() < frameSize) {
1120     // Use the headroom in payload for the frame header.
1121     // Make it appear that the payload IOBuf is empty and retreat so
1122     // appender can access the headroom
1123     payloadLength = payload->length();
1124     payload->trimEnd(payloadLength);
1125     payload->retreat(frameSize);
1126     auto tail = payload->pop();
1127     writeBuf.append(std::move(payload));
1128     payload = std::move(tail);
1129   }
1130   QueueAppender cursor(&writeBuf, frameSize);
1131   cursor.writeBE(uint32_t(streamID));
1132   cursor.writeBE(flagsAndLength(flags, length));
1133   writeBuf.postallocate(payloadLength);
1134   writeBuf.append(std::move(payload));
1135   return kFrameSizeDataCommon + length;
1136 }
1137 
parseHeaders(TransportDirection direction,StreamID streamID,StreamID assocStreamID,const HeaderPieceList & inHeaders)1138 unique_ptr<HTTPMessage> SPDYCodec::parseHeaders(
1139     TransportDirection direction,
1140     StreamID streamID,
1141     StreamID assocStreamID,
1142     const HeaderPieceList& inHeaders) {
1143   unique_ptr<HTTPMessage> msg(new HTTPMessage());
1144   HTTPHeaders& headers = msg->getHeaders();
1145   bool newStream = (type_ != spdy::HEADERS);
1146 
1147   bool hasScheme = false;
1148   bool hasPath = false;
1149   bool hasContentLength = false;
1150 
1151   // Number of fields must be even
1152   CHECK_EQ((inHeaders.size() & 1), 0);
1153   for (unsigned i = 0; i < inHeaders.size(); i += 2) {
1154     uint8_t off = 0;
1155     uint32_t len = inHeaders[i].str.size();
1156     if (len > 1 && inHeaders[i].str[0] == ':') {
1157       off = 1; // also signals control header
1158       len--;
1159     }
1160     folly::StringPiece name(inHeaders[i].str, off, len);
1161     folly::StringPiece value = inHeaders[i + 1].str;
1162     VLOG(5) << "Header " << name << ": " << value;
1163     bool nameOk =
1164         CodecUtil::validateHeaderName(name, CodecUtil::HEADER_NAME_STRICT);
1165     bool valueOk = false;
1166     bool isPath = false;
1167     bool isMethod = false;
1168     if (nameOk) {
1169       if (name == "content-length") {
1170         if (hasContentLength) {
1171           throw SPDYStreamFailed(
1172               false, streamID, 400, "Multiple content-length headers");
1173         }
1174         hasContentLength = true;
1175       }
1176       if ((version_ == 2 && name == "url") ||
1177           (version_ == 3 && off && name == "path")) {
1178         valueOk = CodecUtil::validateURL(value, URLValidateMode::STRICT);
1179         isPath = true;
1180         if (hasPath) {
1181           throw SPDYStreamFailed(
1182               false, streamID, 400, "Multiple paths in header");
1183         }
1184         hasPath = true;
1185       } else if ((version_ == 2 || off) && name == "method") {
1186         valueOk = CodecUtil::validateMethod(value);
1187         isMethod = true;
1188         if (value == "CONNECT") {
1189           // We don't support CONNECT request for SPDY
1190           valueOk = false;
1191         }
1192       } else {
1193         valueOk =
1194             CodecUtil::validateHeaderValue(value, CodecUtil::STRICT_COMPAT);
1195       }
1196     }
1197     if (!nameOk || !valueOk) {
1198       if (newStream) {
1199         deliverOnMessageBegin(streamID, assocStreamID, nullptr);
1200       }
1201       partialMsg_ = std::move(msg);
1202       throw SPDYStreamFailed(false, streamID, 400, "Bad header value");
1203     }
1204     bool add = false;
1205     if (off || version_ == 2) {
1206       if (isMethod) {
1207         msg->setMethod(value);
1208       } else if (isPath) {
1209         msg->setURL(value.str(), /*strict=*/true);
1210       } else if (name == "version") {
1211         if (caseInsensitiveEqual(value, "http/1.0")) {
1212           msg->setHTTPVersion(1, 0);
1213         } else {
1214           msg->setHTTPVersion(1, 1);
1215         }
1216       } else if (version_ == 3 && name == "host") {
1217         headers.add(HTTP_HEADER_HOST, value.str());
1218       } else if (name == "scheme") {
1219         hasScheme = true;
1220         if (value == "https") {
1221           msg->setSecure(true);
1222         }
1223       } else if (name == "status") {
1224         if (direction == TransportDirection::UPSTREAM && !assocStreamID) {
1225           folly::StringPiece codePiece;
1226           folly::StringPiece reasonPiece;
1227           if (value.contains(' ')) {
1228             folly::split<false>(' ', value, codePiece, reasonPiece);
1229           } else {
1230             codePiece = value;
1231           }
1232           int32_t code = -1;
1233           try {
1234             code = folly::to<unsigned int>(codePiece);
1235           } catch (const std::range_error&) {
1236             // Toss out the range error cause the exception will get it
1237           }
1238           if (code >= 100 && code <= 999) {
1239             msg->setStatusCode(code);
1240             msg->setStatusMessage(reasonPiece.str());
1241           } else {
1242             msg->setStatusCode(0);
1243             headers.add(name, value);
1244             partialMsg_ = std::move(msg);
1245             throw SPDYStreamFailed(newStream,
1246                                    streamID,
1247                                    spdy::RST_PROTOCOL_ERROR,
1248                                    "Invalid status code");
1249           }
1250         } else if (!assocStreamID) {
1251           if (version_ == 2) {
1252             headers.add("Status", value);
1253           }
1254         } else { // is a push status since there is an assocStreamID?
1255           // If there exists a push status, save it.
1256           // If there does not, for now, we *eat* the push status.
1257           if (value.size() > 0) {
1258             int16_t code = -1;
1259             try {
1260               code = folly::to<uint16_t>(value);
1261             } catch (const std::range_error&) {
1262               // eat the push status
1263             }
1264             if (code >= 100 && code <= 999) {
1265               msg->setPushStatusCode(code);
1266             } else {
1267               // eat the push status.
1268             }
1269           }
1270         }
1271       } else if (version_ == 2) {
1272         add = true;
1273       }
1274     } else {
1275       add = true;
1276     }
1277     if (add) {
1278       if (!inHeaders[i].isMultiValued() && headers.exists(name)) {
1279         headers.add(name, value);
1280         partialMsg_ = std::move(msg);
1281         throw SPDYStreamFailed(newStream,
1282                                streamID,
1283                                spdy::RST_PROTOCOL_ERROR,
1284                                "Duplicate header value");
1285       }
1286       headers.add(name, value);
1287     }
1288   }
1289   if (assocStreamID &&
1290       (!headers.exists(HTTP_HEADER_HOST) || !hasScheme || !hasPath)) {
1291     // Fail a server push without host, scheme or path headers
1292     throw SPDYStreamFailed(newStream, streamID, 400, "Bad Request");
1293   }
1294   if (direction == TransportDirection::DOWNSTREAM) {
1295     if (version_ == 2 && !headers.exists(HTTP_HEADER_HOST)) {
1296       auto url = ParseURL::parseURL(msg->getURL(), /*strict=*/true);
1297       if (url) {
1298         headers.add(HTTP_HEADER_HOST, url->hostAndPort());
1299       }
1300     }
1301 
1302     const string& accept_encoding =
1303         headers.getSingleOrEmpty(HTTP_HEADER_ACCEPT_ENCODING);
1304     if (accept_encoding.empty()) {
1305       headers.add(HTTP_HEADER_ACCEPT_ENCODING, "gzip, deflate");
1306     } else {
1307       bool hasGzip = false;
1308       bool hasDeflate = false;
1309       if (!CodecUtil::hasGzipAndDeflate(accept_encoding, hasGzip, hasDeflate)) {
1310         string new_encoding = accept_encoding;
1311         if (!hasGzip) {
1312           new_encoding.append(", gzip");
1313         }
1314         if (!hasDeflate) {
1315           new_encoding.append(", deflate");
1316         }
1317         headers.set(HTTP_HEADER_ACCEPT_ENCODING, new_encoding);
1318       }
1319     }
1320   }
1321   return msg;
1322 }
1323 
onSynCommon(StreamID streamID,StreamID assocStreamID,const HeaderPieceList & headers,int8_t pri,const HTTPHeaderSize & size)1324 void SPDYCodec::onSynCommon(StreamID streamID,
1325                             StreamID assocStreamID,
1326                             const HeaderPieceList& headers,
1327                             int8_t pri,
1328                             const HTTPHeaderSize& size) {
1329   if (version_ != versionSettings_.majorVersion) {
1330     LOG(ERROR) << "Invalid version=" << version_;
1331     throw SPDYSessionFailed(spdy::GOAWAY_PROTOCOL_ERROR);
1332   }
1333 
1334   unique_ptr<HTTPMessage> msg =
1335       parseHeaders(transportDirection_, streamID, assocStreamID, headers);
1336   msg->setIngressHeaderSize(size);
1337 
1338   msg->setAdvancedProtocolString(versionSettings_.protocolVersionString);
1339   // Normalize priority to 3 bits in HTTPMessage.
1340   pri <<= (3 - versionSettings_.majorVersion);
1341   msg->setPriority(pri);
1342   msg->setHTTP2Priority(
1343       std::make_tuple(mapPriorityToDependency(pri), false, 255));
1344   deliverOnMessageBegin(streamID, assocStreamID, msg.get());
1345 
1346   if ((flags_ & spdy::CTRL_FLAG_FIN) == 0) {
1347     // If it there are DATA frames coming, consider it chunked
1348     msg->setIsChunked(true);
1349   }
1350   if (userAgent_.empty()) {
1351     userAgent_ = msg->getHeaders().getSingleOrEmpty(HTTP_HEADER_USER_AGENT);
1352   }
1353   deliverCallbackIfAllowed(&HTTPCodec::Callback::onHeadersComplete,
1354                            "onHeadersComplete",
1355                            streamID,
1356                            std::move(msg));
1357 }
1358 
deliverOnMessageBegin(StreamID streamID,StreamID assocStreamID,HTTPMessage * msg)1359 void SPDYCodec::deliverOnMessageBegin(StreamID streamID,
1360                                       StreamID assocStreamID,
1361                                       HTTPMessage* msg) {
1362   if (assocStreamID) {
1363     deliverCallbackIfAllowed(&HTTPCodec::Callback::onPushMessageBegin,
1364                              "onPushMessageBegin",
1365                              streamID,
1366                              assocStreamID,
1367                              msg);
1368   } else {
1369     deliverCallbackIfAllowed(
1370         &HTTPCodec::Callback::onMessageBegin, "onMessageBegin", streamID, msg);
1371   }
1372 }
1373 
onSynStream(uint32_t assocStream,uint8_t pri,uint8_t,const HeaderPieceList & headers,const HTTPHeaderSize & size)1374 void SPDYCodec::onSynStream(uint32_t assocStream,
1375                             uint8_t pri,
1376                             uint8_t /*slot*/,
1377                             const HeaderPieceList& headers,
1378                             const HTTPHeaderSize& size) {
1379   VLOG(4) << "Got SYN_STREAM, stream=" << streamId_
1380           << " pri=" << folly::to<int>(pri);
1381   if (streamId_ == NoStream || streamId_ < lastStreamID_ ||
1382       (transportDirection_ == TransportDirection::UPSTREAM &&
1383        (streamId_ & 0x01) == 1) ||
1384       (transportDirection_ == TransportDirection::DOWNSTREAM &&
1385        ((streamId_ & 0x1) == 0)) ||
1386       (transportDirection_ == TransportDirection::UPSTREAM &&
1387        assocStream == NoStream)) {
1388     LOG(ERROR) << " invalid syn stream stream_id=" << streamId_
1389                << " lastStreamID_=" << lastStreamID_
1390                << " assocStreamID=" << assocStream
1391                << " direction=" << transportDirection_;
1392     throw SPDYSessionFailed(spdy::GOAWAY_PROTOCOL_ERROR);
1393   }
1394 
1395   if (streamId_ == lastStreamID_) {
1396     throw SPDYStreamFailed(true, streamId_, spdy::RST_PROTOCOL_ERROR);
1397   }
1398   if (callback_->numIncomingStreams() >=
1399       egressSettings_.getSetting(SettingsId::MAX_CONCURRENT_STREAMS,
1400                                  spdy::kMaxConcurrentStreams)) {
1401     throw SPDYStreamFailed(true, streamId_, spdy::RST_REFUSED_STREAM);
1402   }
1403   if (assocStream != NoStream && !(flags_ & spdy::CTRL_FLAG_UNIDIRECTIONAL)) {
1404     throw SPDYStreamFailed(true, streamId_, spdy::RST_PROTOCOL_ERROR);
1405   }
1406   if (sessionClosing_ != ClosingState::CLOSING) {
1407     lastStreamID_ = streamId_;
1408   }
1409   onSynCommon(StreamID(streamId_), StreamID(assocStream), headers, pri, size);
1410 }
1411 
onSynReply(const HeaderPieceList & headers,const HTTPHeaderSize & size)1412 void SPDYCodec::onSynReply(const HeaderPieceList& headers,
1413                            const HTTPHeaderSize& size) {
1414   VLOG(4) << "Got SYN_REPLY, stream=" << streamId_;
1415   if (transportDirection_ == TransportDirection::DOWNSTREAM ||
1416       (streamId_ & 0x1) == 0) {
1417     throw SPDYStreamFailed(true, streamId_, spdy::RST_PROTOCOL_ERROR);
1418   }
1419   // Server push transactions, short of any better heuristics,
1420   // should have a background priority. Thus, we pick the largest
1421   // numerical value for the SPDY priority, which no matter what
1422   // protocol version this is can be conveyed to onSynCommon by -1.
1423   onSynCommon(StreamID(streamId_), NoStream, headers, -1, size);
1424 }
1425 
onRstStream(uint32_t statusCode)1426 void SPDYCodec::onRstStream(uint32_t statusCode) noexcept {
1427   VLOG(4) << "Got RST_STREAM, stream=" << streamId_
1428           << ", status=" << statusCode;
1429   StreamID streamID(streamId_);
1430   deliverCallbackIfAllowed(
1431       &HTTPCodec::Callback::onAbort,
1432       "onAbort",
1433       streamID,
1434       spdy::rstToErrorCode(spdy::ResetStatusCode(statusCode)));
1435 }
1436 
onSettings(const SettingList & settings)1437 void SPDYCodec::onSettings(const SettingList& settings) {
1438   VLOG(4) << "Got " << settings.size() << " settings with "
1439           << "version=" << version_ << " and flags=" << std::hex
1440           << folly::to<unsigned int>(flags_) << std::dec;
1441   SettingsList settingsList;
1442   for (const SettingData& cur : settings) {
1443     // For now, we never ask for anything to be persisted, so ignore anything
1444     // coming back
1445     if (cur.flags & spdy::ID_FLAG_SETTINGS_PERSISTED) {
1446       VLOG(2) << "Ignoring bogus persisted setting: " << cur.id;
1447       continue;
1448     }
1449 
1450     switch (cur.id) {
1451       case spdy::SETTINGS_UPLOAD_BANDWIDTH:
1452       case spdy::SETTINGS_DOWNLOAD_BANDWIDTH:
1453       case spdy::SETTINGS_ROUND_TRIP_TIME:
1454       case spdy::SETTINGS_CURRENT_CWND:
1455       case spdy::SETTINGS_DOWNLOAD_RETRANS_RATE:
1456       case spdy::SETTINGS_CLIENT_CERTIFICATE_VECTOR_SIZE:
1457         // These will be stored in ingressSettings_ and passed to the callback
1458         // but we currently ignore the PERSIST flag
1459         break;
1460       case spdy::SETTINGS_MAX_CONCURRENT_STREAMS:
1461         break;
1462       case spdy::SETTINGS_INITIAL_WINDOW_SIZE:
1463         if (cur.value > std::numeric_limits<int32_t>::max()) {
1464           throw SPDYSessionFailed(spdy::GOAWAY_PROTOCOL_ERROR);
1465         }
1466         break;
1467       default:
1468         LOG(ERROR) << "Received unknown setting with ID=" << cur.id
1469                    << ", value=" << cur.value << ", and flags=" << std::hex
1470                    << cur.flags << std::dec;
1471     }
1472     if (cur.id >= spdy::SettingsId::SETTINGS_UPLOAD_BANDWIDTH &&
1473         cur.id <= spdy::SettingsId::SETTINGS_CLIENT_CERTIFICATE_VECTOR_SIZE) {
1474       auto id = spdy::spdyToHttpSettingsId((spdy::SettingsId)cur.id);
1475       if (id) {
1476         ingressSettings_.setSetting(*id, cur.value);
1477         auto s = ingressSettings_.getSetting(*id);
1478         settingsList.push_back(*s);
1479       }
1480     }
1481   }
1482   callback_->onSettings(settingsList);
1483 }
1484 
onPing(uint32_t data)1485 void SPDYCodec::onPing(uint32_t data) noexcept {
1486   bool odd = data & 0x1;
1487   bool isReply = true;
1488   if (transportDirection_ == TransportDirection::DOWNSTREAM) {
1489     if (odd) {
1490       isReply = false;
1491     }
1492   } else if (!odd) {
1493     isReply = false;
1494   }
1495 
1496   if (isReply) {
1497     if (data >= nextEgressPingID_) {
1498       LOG(INFO) << "Received reply for pingID=" << data
1499                 << " that was never sent";
1500       return;
1501     }
1502     callback_->onPingReply(data);
1503   } else {
1504     callback_->onPingRequest(data);
1505   }
1506 }
1507 
onGoaway(uint32_t lastGoodStream,uint32_t statusCode)1508 void SPDYCodec::onGoaway(uint32_t lastGoodStream,
1509                          uint32_t statusCode) noexcept {
1510   VLOG(4) << "Got GOAWAY, lastGoodStream=" << lastGoodStream
1511           << ", statusCode=" << statusCode;
1512 
1513   if (lastGoodStream < ingressGoawayAck_) {
1514     ingressGoawayAck_ = lastGoodStream;
1515     // Drain all streams <= lastGoodStream
1516     // and abort streams > lastGoodStream
1517     auto errorCode = ErrorCode::PROTOCOL_ERROR;
1518     if (statusCode <= spdy::GoawayStatusCode::GOAWAY_FLOW_CONTROL_ERROR) {
1519       errorCode = spdy::goawayToErrorCode(spdy::GoawayStatusCode(statusCode));
1520     }
1521     callback_->onGoaway(lastGoodStream, errorCode);
1522   } else {
1523     LOG(WARNING) << "Received multiple GOAWAY with increasing ack";
1524   }
1525 }
1526 
onHeaders(const HeaderPieceList &)1527 void SPDYCodec::onHeaders(const HeaderPieceList& /*headers*/) noexcept {
1528   VLOG(3) << "onHeaders is unimplemented.";
1529 }
1530 
onWindowUpdate(uint32_t delta)1531 void SPDYCodec::onWindowUpdate(uint32_t delta) noexcept {
1532   deliverCallbackIfAllowed(
1533       &HTTPCodec::Callback::onWindowUpdate, "onWindowUpdate", streamId_, delta);
1534 }
1535 
failStream(bool newStream,StreamID streamID,uint32_t code,string excStr)1536 void SPDYCodec::failStream(bool newStream,
1537                            StreamID streamID,
1538                            uint32_t code,
1539                            string excStr) {
1540   // Suppress any EOM callback for the current frame.
1541   if (streamID == streamId_) {
1542     flags_ &= ~spdy::CTRL_FLAG_FIN;
1543   }
1544 
1545   HTTPException err(code >= 100 ? HTTPException::Direction::INGRESS
1546                                 : HTTPException::Direction::INGRESS_AND_EGRESS,
1547                     folly::to<std::string>("SPDYCodec stream error: stream=",
1548                                            streamID,
1549                                            " status=",
1550                                            code,
1551                                            " exception: ",
1552                                            excStr));
1553   if (code >= 100) {
1554     err.setHttpStatusCode(code);
1555   } else {
1556     err.setCodecStatusCode(spdy::rstToErrorCode(spdy::ResetStatusCode(code)));
1557   }
1558   err.setProxygenError(kErrorParseHeader);
1559 
1560   if (partialMsg_) {
1561     err.setPartialMsg(std::move(partialMsg_));
1562   }
1563   // store the ingress buffer
1564   if (currentIngressBuf_) {
1565     err.setCurrentIngressBuf(currentIngressBuf_->clone());
1566   }
1567   callback_->onError(streamID, err, newStream);
1568 }
1569 
failSession(uint32_t code)1570 void SPDYCodec::failSession(uint32_t code) {
1571   HTTPException err(HTTPException::Direction::INGRESS_AND_EGRESS,
1572                     folly::to<std::string>("SPDYCodec session error: "
1573                                            "lastGoodStream=",
1574                                            lastStreamID_,
1575                                            " status=",
1576                                            code));
1577   err.setCodecStatusCode(spdy::goawayToErrorCode(spdy::GoawayStatusCode(code)));
1578   err.setProxygenError(kErrorParseHeader);
1579 
1580   // store the ingress buffer
1581   if (currentIngressBuf_) {
1582     err.setCurrentIngressBuf(currentIngressBuf_->clone());
1583   }
1584   callback_->onError(0, err);
1585 }
1586 
rstStatusSupported(int statusCode) const1587 bool SPDYCodec::rstStatusSupported(int statusCode) const {
1588   if (statusCode == 0) {
1589     // 0 is not a valid status code for RST_STREAM
1590     return false;
1591   }
1592   // SPDY/3 supports more status codes for RST_STREAM. For SPDY/2,
1593   // we just use PROTOCOL_ERROR for these new higher numbered error codes.
1594   return (versionSettings_.majorVersion != 2 ||
1595           statusCode <= spdy::RST_FLOW_CONTROL_ERROR);
1596 }
1597 
getVersion(const std::string & protocol)1598 folly::Optional<SPDYVersion> SPDYCodec::getVersion(
1599     const std::string& protocol) {
1600   // Fail fast if it's not possible for the protocol string to define a
1601   // SPDY protocol. strlen("spdy/1") == 6
1602   if (protocol.length() < 6) {
1603     return folly::none;
1604   }
1605 
1606   if (protocol == "spdy/3.1") {
1607     return SPDYVersion::SPDY3_1;
1608   }
1609   if (protocol == "spdy/3") {
1610     return SPDYVersion::SPDY3;
1611   }
1612 
1613   return folly::none;
1614 }
1615 
1616 } // namespace proxygen
1617