@@ -174,6 +174,18 @@ Http2Options::Http2Options(Environment* env) {
174174  if  (flags & (1  << IDX_OPTIONS_MAX_OUTSTANDING_SETTINGS)) {
175175    SetMaxOutstandingSettings (buffer[IDX_OPTIONS_MAX_OUTSTANDING_SETTINGS]);
176176  }
177+ 
178+   //  The HTTP2 specification places no limits on the amount of memory
179+   //  that a session can consume. In order to prevent abuse, we place a
180+   //  cap on the amount of memory a session can consume at any given time.
181+   //  this is a credit based system. Existing streams may cause the limit
182+   //  to be temporarily exceeded but once over the limit, new streams cannot
183+   //  created.
184+   //  Important: The maxSessionMemory option in javascript is expressed in
185+   //             terms of MB increments (i.e. the value 1 == 1 MB)
186+   if  (flags & (1  << IDX_OPTIONS_MAX_SESSION_MEMORY)) {
187+     SetMaxSessionMemory (buffer[IDX_OPTIONS_MAX_SESSION_MEMORY] * 1e6 );
188+   }
177189}
178190
179191void  Http2Session::Http2Settings::Init () {
@@ -482,11 +494,13 @@ Http2Session::Http2Session(Environment* env,
482494  //  Capture the configuration options for this session
483495  Http2Options opts (env);
484496
485-   int32_t  maxHeaderPairs = opts.GetMaxHeaderPairs ();
497+   max_session_memory_ = opts.GetMaxSessionMemory ();
498+ 
499+   uint32_t  maxHeaderPairs = opts.GetMaxHeaderPairs ();
486500  max_header_pairs_ =
487501      type == NGHTTP2_SESSION_SERVER
488-           ? std::max (maxHeaderPairs, 4 )     //  minimum # of request headers
489-           : std::max (maxHeaderPairs, 1 );    //  minimum # of response headers
502+           ? std::max (maxHeaderPairs, 4U )     //  minimum # of request headers
503+           : std::max (maxHeaderPairs, 1U );    //  minimum # of response headers
490504
491505  max_outstanding_pings_ = opts.GetMaxOutstandingPings ();
492506  max_outstanding_settings_ = opts.GetMaxOutstandingSettings ();
@@ -672,18 +686,21 @@ inline bool Http2Session::CanAddStream() {
672686  size_t  maxSize =
673687      std::min (streams_.max_size (), static_cast <size_t >(maxConcurrentStreams));
674688  //  We can add a new stream so long as we are less than the current
675-   //  maximum on concurrent streams
676-   return  streams_.size () < maxSize;
689+   //  maximum on concurrent streams and there's enough available memory
690+   return  streams_.size () < maxSize &&
691+          IsAvailableSessionMemory (sizeof (Http2Stream));
677692}
678693
679694inline  void  Http2Session::AddStream (Http2Stream* stream) {
680695  CHECK_GE (++statistics_.stream_count , 0 );
681696  streams_[stream->id ()] = stream;
697+   IncrementCurrentSessionMemory (stream->self_size ());
682698}
683699
684700
685- inline  void  Http2Session::RemoveStream (int32_t  id) {
686-   streams_.erase (id);
701+ inline  void  Http2Session::RemoveStream (Http2Stream* stream) {
702+   streams_.erase (stream->id ());
703+   DecrementCurrentSessionMemory (stream->self_size ());
687704}
688705
689706//  Used as one of the Padding Strategy functions. Will attempt to ensure
@@ -1677,7 +1694,7 @@ Http2Stream::Http2Stream(
16771694
16781695Http2Stream::~Http2Stream () {
16791696  if  (session_ != nullptr ) {
1680-     session_->RemoveStream (id_ );
1697+     session_->RemoveStream (this );
16811698    session_ = nullptr ;
16821699  }
16831700
@@ -2007,7 +2024,7 @@ inline int Http2Stream::DoWrite(WriteWrap* req_wrap,
20072024      i == nbufs - 1  ? req_wrap : nullptr ,
20082025      bufs[i]
20092026    });
2010-     available_outbound_length_ +=  bufs[i].len ;
2027+     IncrementAvailableOutboundLength ( bufs[i].len ) ;
20112028  }
20122029  CHECK_NE (nghttp2_session_resume_data (**session_, id_), NGHTTP2_ERR_NOMEM);
20132030  return  0 ;
@@ -2029,7 +2046,10 @@ inline bool Http2Stream::AddHeader(nghttp2_rcbuf* name,
20292046  if  (this ->statistics_ .first_header  == 0 )
20302047    this ->statistics_ .first_header  = uv_hrtime ();
20312048  size_t  length = GetBufferLength (name) + GetBufferLength (value) + 32 ;
2032-   if  (current_headers_.size () == max_header_pairs_ ||
2049+   //  A header can only be added if we have not exceeded the maximum number
2050+   //  of headers and the session has memory available for it.
2051+   if  (!session_->IsAvailableSessionMemory (length) ||
2052+       current_headers_.size () == max_header_pairs_ ||
20332053      current_headers_length_ + length > max_header_length_) {
20342054    return  false ;
20352055  }
@@ -2173,7 +2193,7 @@ ssize_t Http2Stream::Provider::Stream::OnRead(nghttp2_session* handle,
21732193      //  Just return the length, let Http2Session::OnSendData take care of
21742194      //  actually taking the buffers out of the queue.
21752195      *flags |= NGHTTP2_DATA_FLAG_NO_COPY;
2176-       stream->available_outbound_length_  -=  amount;
2196+       stream->DecrementAvailableOutboundLength ( amount) ;
21772197    }
21782198  }
21792199
@@ -2196,6 +2216,15 @@ ssize_t Http2Stream::Provider::Stream::OnRead(nghttp2_session* handle,
21962216  return  amount;
21972217}
21982218
2219+ inline  void  Http2Stream::IncrementAvailableOutboundLength (size_t  amount) {
2220+   available_outbound_length_ += amount;
2221+   session_->IncrementCurrentSessionMemory (amount);
2222+ }
2223+ 
2224+ inline  void  Http2Stream::DecrementAvailableOutboundLength (size_t  amount) {
2225+   available_outbound_length_ -= amount;
2226+   session_->DecrementCurrentSessionMemory (amount);
2227+ }
21992228
22002229
22012230//  Implementation of the JavaScript API
@@ -2689,6 +2718,7 @@ Http2Session::Http2Ping* Http2Session::PopPing() {
26892718  if  (!outstanding_pings_.empty ()) {
26902719    ping = outstanding_pings_.front ();
26912720    outstanding_pings_.pop ();
2721+     DecrementCurrentSessionMemory (ping->self_size ());
26922722  }
26932723  return  ping;
26942724}
@@ -2697,6 +2727,7 @@ bool Http2Session::AddPing(Http2Session::Http2Ping* ping) {
26972727  if  (outstanding_pings_.size () == max_outstanding_pings_)
26982728    return  false ;
26992729  outstanding_pings_.push (ping);
2730+   IncrementCurrentSessionMemory (ping->self_size ());
27002731  return  true ;
27012732}
27022733
@@ -2705,6 +2736,7 @@ Http2Session::Http2Settings* Http2Session::PopSettings() {
27052736  if  (!outstanding_settings_.empty ()) {
27062737    settings = outstanding_settings_.front ();
27072738    outstanding_settings_.pop ();
2739+     DecrementCurrentSessionMemory (settings->self_size ());
27082740  }
27092741  return  settings;
27102742}
@@ -2713,6 +2745,7 @@ bool Http2Session::AddSettings(Http2Session::Http2Settings* settings) {
27132745  if  (outstanding_settings_.size () == max_outstanding_settings_)
27142746    return  false ;
27152747  outstanding_settings_.push (settings);
2748+   IncrementCurrentSessionMemory (settings->self_size ());
27162749  return  true ;
27172750}
27182751
0 commit comments