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