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 #pragma once 10 11 #include <bitset> 12 #include <deque> 13 #include <folly/Optional.h> 14 #include <proxygen/lib/http/HTTPHeaders.h> 15 #include <proxygen/lib/http/codec/HTTPCodec.h> 16 #include <proxygen/lib/http/codec/HTTPParallelCodec.h> 17 #include <proxygen/lib/http/codec/HTTPSettings.h> 18 #include <proxygen/lib/http/codec/SPDYConstants.h> 19 #include <proxygen/lib/http/codec/SPDYVersionSettings.h> 20 #include <proxygen/lib/http/codec/compress/GzipHeaderCodec.h> 21 #include <proxygen/lib/http/codec/compress/HPACKCodec.h> 22 #include <zlib.h> 23 24 namespace folly { namespace io { 25 class Cursor; 26 }} // namespace folly::io 27 28 namespace proxygen { 29 30 /** 31 * An implementation of the framing layer for all versions of 32 * SPDY. Instances of this class must not be used from multiple threads 33 * concurrently. 34 */ 35 class SPDYCodec : public HTTPParallelCodec { 36 public: 37 static const StreamID NoStream = 0; 38 39 explicit SPDYCodec(TransportDirection direction, 40 SPDYVersion version, 41 int spdyCompressionLevel = Z_NO_COMPRESSION); 42 ~SPDYCodec() override; 43 44 static const SPDYVersionSettings& getVersionSettings(SPDYVersion version); 45 46 // HTTPCodec API 47 CodecProtocol getProtocol() const override; 48 const std::string& getUserAgent() const override; 49 bool supportsStreamFlowControl() const override; 50 bool supportsSessionFlowControl() const override; 51 size_t onIngress(const folly::IOBuf& buf) override; supportsPushTransactions()52 bool supportsPushTransactions() const override { 53 return true; 54 } 55 void generateHeader( 56 folly::IOBufQueue& writeBuf, 57 StreamID stream, 58 const HTTPMessage& msg, 59 bool eom = false, 60 HTTPHeaderSize* size = nullptr, 61 const folly::Optional<HTTPHeaders>& extraHeaders = folly::none) override; 62 void generatePushPromise(folly::IOBufQueue& writeBuf, 63 StreamID stream, 64 const HTTPMessage& msg, 65 StreamID assocstream, 66 bool eom = false, 67 HTTPHeaderSize* size = nullptr) override; 68 size_t generateBody(folly::IOBufQueue& writeBuf, 69 StreamID stream, 70 std::unique_ptr<folly::IOBuf> chain, 71 folly::Optional<uint8_t> padding, 72 bool eom) override; 73 size_t generateChunkHeader(folly::IOBufQueue& writeBuf, 74 StreamID stream, 75 size_t length) override; 76 size_t generateChunkTerminator(folly::IOBufQueue& writeBuf, 77 StreamID stream) override; 78 size_t generateTrailers(folly::IOBufQueue& writeBuf, 79 StreamID stream, 80 const HTTPHeaders& trailers) override; 81 size_t generateEOM(folly::IOBufQueue& writeBuf, StreamID stream) override; 82 size_t generateRstStream(folly::IOBufQueue& writeBuf, 83 StreamID txn, 84 ErrorCode statusCode) override; 85 size_t generateGoaway( 86 folly::IOBufQueue& writeBuf, 87 StreamID lastStream, 88 ErrorCode statusCode, 89 std::unique_ptr<folly::IOBuf> debugData = nullptr) override; 90 size_t generatePingRequest( 91 folly::IOBufQueue& writeBuf, 92 folly::Optional<uint64_t> data = folly::none) override; 93 size_t generatePingReply(folly::IOBufQueue& writeBuf, uint64_t data) override; 94 size_t generateSettings(folly::IOBufQueue& writeBuf) override; 95 size_t generateWindowUpdate(folly::IOBufQueue& writeBuf, 96 StreamID stream, 97 uint32_t delta) override; 98 99 /** 100 * Returns a const reference to the ingress settings. Since ingress 101 * settings are set by the remote end, it doesn't make sense for these 102 * to be mutable outside the codec. 103 */ getIngressSettings()104 const HTTPSettings* getIngressSettings() const override { 105 return &ingressSettings_; 106 } 107 /** 108 * Returns a reference to the egress settings 109 */ getEgressSettings()110 HTTPSettings* getEgressSettings() override { 111 return &egressSettings_; 112 } getDefaultWindowSize()113 uint32_t getDefaultWindowSize() const override { 114 return spdy::kInitialWindow; 115 } 116 117 uint8_t getVersion() const; 118 119 uint8_t getMinorVersion() const; 120 121 void setMaxFrameLength(uint32_t maxFrameLength); 122 123 /** 124 * Set the maximum size of the uncompressed headers 125 */ 126 void setMaxUncompressedHeaders(uint32_t maxUncompressed); 127 setHeaderCodecStats(HeaderCodec::Stats * stats)128 void setHeaderCodecStats(HeaderCodec::Stats* stats) override { 129 headerCodec_.setStats(stats); 130 } 131 132 size_t addPriorityNodes(PriorityQueue& queue, 133 folly::IOBufQueue& writeBuf, 134 uint8_t maxLevel) override; 135 136 StreamID mapPriorityToDependency(uint8_t priority) const override; 137 138 int8_t mapDependencyToPriority(StreamID parent) const override; 139 140 struct SettingData { SettingDataSettingData141 SettingData(uint8_t inFlags, uint32_t inId, uint32_t inValue) 142 : flags(inFlags), id(inId), value(inValue) { 143 } 144 uint8_t flags; 145 uint32_t id; 146 uint32_t value; 147 }; 148 149 using SettingList = std::vector<SettingData>; 150 151 /** 152 * Returns the SPDYVersion for the given protocol string, or none otherwise. 153 */ 154 static folly::Optional<SPDYVersion> getVersion(const std::string& protocol); 155 156 private: 157 /** 158 * Generates a frame of type SYN_STREAM 159 */ 160 void generateSynStream( 161 StreamID stream, 162 StreamID assocStream, 163 folly::IOBufQueue& writeBuf, 164 const HTTPMessage& msg, 165 bool eom, 166 HTTPHeaderSize* size, 167 const folly::Optional<HTTPHeaders>& extraHeaders = folly::none); 168 /** 169 * Generates a frame of type SYN_REPLY 170 */ 171 void generateSynReply( 172 StreamID stream, 173 folly::IOBufQueue& writeBuf, 174 const HTTPMessage& msg, 175 bool eom, 176 HTTPHeaderSize* size, 177 const folly::Optional<HTTPHeaders>& extraHeaders = folly::none); 178 179 /** 180 * Generates the shared parts of a ping request and reply. 181 */ 182 size_t generatePingCommon(folly::IOBufQueue& writeBuf, uint64_t data); 183 /** 184 * Ingress parser, can throw exceptions 185 */ 186 size_t parseIngress(const folly::IOBuf& buf); 187 188 /** 189 * Handle an ingress SYN_STREAM control frame. For a downstream-facing 190 * SPDY session, this frame is the equivalent of an HTTP request header. 191 */ 192 void onSynStream(uint32_t assocStream, 193 uint8_t pri, 194 uint8_t slot, 195 const compress::HeaderPieceList& headers, 196 const HTTPHeaderSize& size); 197 /** 198 * Handle an ingress SYN_REPLY control frame. For an upstream-facing 199 * SPDY session, this frame is the equivalent of an HTTP response header. 200 */ 201 void onSynReply(const compress::HeaderPieceList& headers, 202 const HTTPHeaderSize& size); 203 /** 204 * Handle an ingress RST_STREAM control frame. 205 */ 206 void onRstStream(uint32_t statusCode) noexcept; 207 /** 208 * Handle a SETTINGS message that changes/updates settings for the 209 * entire SPDY connection (across all transactions) 210 */ 211 void onSettings(const SettingList& settings); 212 213 void onPing(uint32_t data) noexcept; 214 215 void onGoaway(uint32_t lastGoodStream, uint32_t statusCode) noexcept; 216 /** 217 * Handle a HEADERS frame. This is *not* invoked when the first headers 218 * on a stream are received. This is called when the remote endpoint 219 * sends us any additional headers. 220 */ 221 void onHeaders(const compress::HeaderPieceList& headers) noexcept; 222 223 void onWindowUpdate(uint32_t delta) noexcept; 224 225 // Helpers 226 227 /** 228 * Parses the headers in the nameValues array and creates an HTTPMessage 229 * object initialized for this transaction. 230 */ 231 std::unique_ptr<HTTPMessage> parseHeaders( 232 TransportDirection direction, 233 StreamID streamID, 234 StreamID assocStreamID, 235 const compress::HeaderPieceList& headers); 236 237 /** 238 * Helper function to parse out a control frame and execute its handler. 239 * All errors are thrown as exceptions. 240 */ 241 void onControlFrame(folly::io::Cursor& cursor); 242 243 /** 244 * Helper function that contains the common implementation details of 245 * calling the same callbacks for onSynStream() and onSynReply() 246 * 247 * Negative values of pri are interpreted much like negative array 248 * indexes in python, so -1 will be the largest numerical priority 249 * value for this SPDY version (i.e., 3 for SPDY/2 or 7 for SPDY/3), 250 * -2 the second largest (i.e., 2 for SPDY/2 or 6 for SPDY/3). 251 */ 252 void onSynCommon(StreamID streamID, 253 StreamID assocStreamID, 254 const compress::HeaderPieceList& headers, 255 int8_t pri, 256 const HTTPHeaderSize& size); 257 258 void deliverOnMessageBegin(StreamID streamID, 259 StreamID assocStreamID, 260 HTTPMessage* msg); 261 262 /** 263 * Generate the header for a SPDY data frame 264 * @param writeBuf Buffer queue to which the control frame is written. 265 * @param streamID Stream ID. 266 * @param flags Bitmap of flags, as defined in the SPDY spec. 267 * @param length Length of the data, in bytes. 268 * @return length Length of the encoded bytes 269 * @return payload data payload 270 */ 271 size_t generateDataFrame(folly::IOBufQueue& writeBuf, 272 uint32_t streamID, 273 uint8_t flags, 274 uint32_t length, 275 std::unique_ptr<folly::IOBuf> payload); 276 277 /** 278 * Serializes headers for requests (aka SYN_STREAM) 279 * @param msg The message to serialize. 280 * @param isPushed true if this is a push message 281 * @param size Size of the serialized headers before and after compression 282 * @param headroom Optional amount of headroom to reserve at the 283 * front of the returned IOBuf, in case the caller 284 * wants to put some other data there. 285 * @param extraHeaders Optional extra headers to be send together with the 286 * msg. 287 */ 288 std::unique_ptr<folly::IOBuf> serializeRequestHeaders( 289 const HTTPMessage& msg, 290 bool isPushed, 291 uint32_t headroom = 0, 292 HTTPHeaderSize* size = nullptr, 293 const folly::Optional<HTTPHeaders>& extraHeaders = folly::none); 294 295 /** 296 * Serializes headers for responses (aka SYN_REPLY) 297 * @param msg The message to serialize. 298 * @param size Size of the serialized headers before and after compression 299 * @param headroom Optional amount of headroom to reserve at the 300 * front of the returned IOBuf, in case the caller 301 * wants to put some other data there. 302 * @param extraHeaders Optional extra headers to be serialized together with 303 * the msg. 304 */ 305 std::unique_ptr<folly::IOBuf> serializeResponseHeaders( 306 const HTTPMessage& msg, 307 uint32_t headroom = 0, 308 HTTPHeaderSize* size = nullptr, 309 const folly::Optional<HTTPHeaders>& extraHeaders = folly::none); 310 311 /** 312 * Helper function to create the compressed Name/Value representation of 313 * a message's headers. 314 * @param msg The message containing headers to serialize. 315 * @param headers A vector containing any extra headers to serialize 316 * @param size Size of the serialized headers before and after compression 317 * @param headroom Optional amount of headroom to reserve at the 318 * front of the returned IOBuf, in case the caller 319 * wants to put some other data there. 320 * @param extraHeaders Optional extra headers to encode together with the msg. 321 */ 322 std::unique_ptr<folly::IOBuf> encodeHeaders( 323 const HTTPMessage& msg, 324 std::vector<compress::Header>& headers, 325 uint32_t headroom = 0, 326 HTTPHeaderSize* size = nullptr, 327 const folly::Optional<HTTPHeaders>& extraHeaders = folly::none); 328 329 void failStream(bool newTxn, 330 StreamID streamID, 331 uint32_t code, 332 std::string excStr = empty_string); 333 334 void failSession(uint32_t statusCode); 335 336 /** 337 * Decodes the headers from the cursor and returns the result. 338 */ 339 HeaderDecodeResult decodeHeaders(folly::io::Cursor& cursor); 340 341 void checkLength(uint32_t expectedLength, const std::string& msg); 342 343 void checkMinLength(uint32_t minLength, const std::string& msg); 344 345 bool isSPDYReserved(const std::string& name); 346 347 /** 348 * Helper function to check if the status code is supported by the 349 * SPDY version being used 350 */ 351 bool rstStatusSupported(int statusCode) const; 352 353 folly::fbvector<StreamID> closedStreams_; 354 const SPDYVersionSettings& versionSettings_; 355 356 HTTPSettings ingressSettings_{ 357 {SettingsId::MAX_CONCURRENT_STREAMS, spdy::kMaxConcurrentStreams}, 358 {SettingsId::INITIAL_WINDOW_SIZE, spdy::kInitialWindow}}; 359 HTTPSettings egressSettings_{ 360 {SettingsId::MAX_CONCURRENT_STREAMS, spdy::kMaxConcurrentStreams}, 361 {SettingsId::INITIAL_WINDOW_SIZE, spdy::kInitialWindow}}; 362 363 std::unique_ptr<HTTPMessage> partialMsg_; 364 std::string userAgent_; 365 const folly::IOBuf* currentIngressBuf_{nullptr}; 366 367 StreamID nextEgressPingID_; 368 // StreamID's are 31 bit unsigned integers, so all received goaways will 369 // be lower than this. 370 371 uint32_t maxFrameLength_{spdy::kMaxFrameLength}; 372 uint32_t streamId_{0}; 373 uint32_t length_{0}; 374 uint16_t version_{0}; 375 uint16_t type_{0xffff}; 376 uint8_t flags_{0}; 377 378 // SPDY Frame parsing state 379 enum class FrameState : uint8_t { 380 FRAME_HEADER = 0, 381 CTRL_FRAME_DATA = 1, 382 DATA_FRAME_DATA = 2, 383 } frameState_ : 2; 384 385 bool ctrl_ : 1; 386 387 GzipHeaderCodec headerCodec_; 388 }; 389 390 } // namespace proxygen 391