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