View Javadoc
1   /*
2    * Copyright 2014 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
5    * "License"); you may not use this file except in compliance with the License. You may obtain a
6    * copy of the License at:
7    *
8    * https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software distributed under the License
11   * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12   * or implied. See the License for the specific language governing permissions and limitations under
13   * the License.
14   */
15  
16  package io.netty.handler.codec.http2;
17  
18  import io.netty.buffer.ByteBuf;
19  import io.netty.channel.ChannelFuture;
20  import io.netty.channel.ChannelPromise;
21  import io.netty.handler.codec.http2.Http2Stream.State;
22  import io.netty.util.collection.IntObjectHashMap;
23  import io.netty.util.collection.IntObjectMap;
24  import io.netty.util.collection.IntObjectMap.PrimitiveEntry;
25  import io.netty.util.concurrent.Future;
26  import io.netty.util.concurrent.Promise;
27  import io.netty.util.concurrent.PromiseNotifier;
28  import io.netty.util.internal.EmptyArrays;
29  import io.netty.util.internal.logging.InternalLogger;
30  import io.netty.util.internal.logging.InternalLoggerFactory;
31  
32  import java.util.ArrayDeque;
33  import java.util.ArrayList;
34  import java.util.Arrays;
35  import java.util.Iterator;
36  import java.util.LinkedHashSet;
37  import java.util.List;
38  import java.util.Queue;
39  import java.util.Set;
40  
41  import static io.netty.handler.codec.http2.Http2CodecUtil.CONNECTION_STREAM_ID;
42  import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_MAX_RESERVED_STREAMS;
43  import static io.netty.handler.codec.http2.Http2Error.INTERNAL_ERROR;
44  import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
45  import static io.netty.handler.codec.http2.Http2Error.REFUSED_STREAM;
46  import static io.netty.handler.codec.http2.Http2Exception.closedStreamError;
47  import static io.netty.handler.codec.http2.Http2Exception.connectionError;
48  import static io.netty.handler.codec.http2.Http2Exception.streamError;
49  import static io.netty.handler.codec.http2.Http2Stream.State.CLOSED;
50  import static io.netty.handler.codec.http2.Http2Stream.State.HALF_CLOSED_LOCAL;
51  import static io.netty.handler.codec.http2.Http2Stream.State.HALF_CLOSED_REMOTE;
52  import static io.netty.handler.codec.http2.Http2Stream.State.IDLE;
53  import static io.netty.handler.codec.http2.Http2Stream.State.OPEN;
54  import static io.netty.handler.codec.http2.Http2Stream.State.RESERVED_LOCAL;
55  import static io.netty.handler.codec.http2.Http2Stream.State.RESERVED_REMOTE;
56  import static io.netty.util.internal.ObjectUtil.checkNotNull;
57  import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;
58  import static java.lang.Integer.MAX_VALUE;
59  
60  /**
61   * Simple implementation of {@link Http2Connection}.
62   */
63  public class DefaultHttp2Connection implements Http2Connection {
64      private static final InternalLogger logger = InternalLoggerFactory.getInstance(DefaultHttp2Connection.class);
65      // Fields accessed by inner classes
66      final IntObjectMap<Http2Stream> streamMap = new IntObjectHashMap<Http2Stream>();
67      final PropertyKeyRegistry propertyKeyRegistry = new PropertyKeyRegistry();
68      final ConnectionStream connectionStream = new ConnectionStream();
69      final DefaultEndpoint<Http2LocalFlowController> localEndpoint;
70      final DefaultEndpoint<Http2RemoteFlowController> remoteEndpoint;
71  
72      /**
73       * We chose a {@link List} over a {@link Set} to avoid allocating an {@link Iterator} objects when iterating over
74       * the listeners.
75       * <p>
76       * Initial size of 4 because the default configuration currently has 3 listeners
77       * (local/remote flow controller and {@link StreamByteDistributor}) and we leave room for 1 extra.
78       * We could be more aggressive but the ArrayList resize will double the size if we are too small.
79       */
80      final List<Listener> listeners = new ArrayList<Listener>(4);
81      final ActiveStreams activeStreams;
82      Promise<Void> closePromise;
83  
84      /**
85       * Creates a new connection with the given settings.
86       * @param server whether or not this end-point is the server-side of the HTTP/2 connection.
87       */
88      public DefaultHttp2Connection(boolean server) {
89          this(server, DEFAULT_MAX_RESERVED_STREAMS);
90      }
91  
92      /**
93       * Creates a new connection with the given settings.
94       * @param server whether or not this end-point is the server-side of the HTTP/2 connection.
95       * @param maxReservedStreams The maximum amount of streams which can exist in the reserved state for each endpoint.
96       */
97      public DefaultHttp2Connection(boolean server, int maxReservedStreams) {
98          activeStreams = new ActiveStreams(listeners);
99          // Reserved streams are excluded from the SETTINGS_MAX_CONCURRENT_STREAMS limit according to [1] and the RFC
100         // doesn't define a way to communicate the limit on reserved streams. We rely upon the peer to send RST_STREAM
101         // in response to any locally enforced limits being exceeded [2].
102         // [1] https://tools.ietf.org/html/rfc7540#section-5.1.2
103         // [2] https://tools.ietf.org/html/rfc7540#section-8.2.2
104         localEndpoint = new DefaultEndpoint<Http2LocalFlowController>(server, server ? MAX_VALUE : maxReservedStreams);
105         remoteEndpoint = new DefaultEndpoint<Http2RemoteFlowController>(!server, maxReservedStreams);
106 
107         // Add the connection stream to the map.
108         streamMap.put(connectionStream.id(), connectionStream);
109     }
110 
111     /**
112      * Determine if {@link #close(Promise)} has been called and no more streams are allowed to be created.
113      */
114     final boolean isClosed() {
115         return closePromise != null;
116     }
117 
118     @Override
119     public Future<Void> close(final Promise<Void> promise) {
120         checkNotNull(promise, "promise");
121         // Since we allow this method to be called multiple times, we must make sure that all the promises are notified
122         // when all streams are removed and the close operation completes.
123         if (closePromise != null) {
124             if (closePromise == promise) {
125                 // Do nothing
126             } else if (promise instanceof ChannelPromise && ((ChannelFuture) closePromise).isVoid()) {
127                 closePromise = promise;
128             } else {
129                 PromiseNotifier.cascade(closePromise, promise);
130             }
131         } else {
132             closePromise = promise;
133         }
134         if (isStreamMapEmpty()) {
135             promise.trySuccess(null);
136             return promise;
137         }
138 
139         Iterator<PrimitiveEntry<Http2Stream>> itr = streamMap.entries().iterator();
140         // We must take care while iterating the streamMap as to not modify while iterating in case there are other code
141         // paths iterating over the active streams.
142         if (activeStreams.allowModifications()) {
143             activeStreams.incrementPendingIterations();
144             try {
145                 while (itr.hasNext()) {
146                     DefaultStream stream = (DefaultStream) itr.next().value();
147                     if (stream.id() != CONNECTION_STREAM_ID) {
148                         // If modifications of the activeStream map is allowed, then a stream close operation will also
149                         // modify the streamMap. Pass the iterator in so that remove will be called to prevent
150                         // concurrent modification exceptions.
151                         stream.close(itr);
152                     }
153                 }
154             } finally {
155                 activeStreams.decrementPendingIterations();
156             }
157         } else {
158             while (itr.hasNext()) {
159                 Http2Stream stream = itr.next().value();
160                 if (stream.id() != CONNECTION_STREAM_ID) {
161                     // We are not allowed to make modifications, so the close calls will be executed after this
162                     // iteration completes.
163                     stream.close();
164                 }
165             }
166         }
167         return closePromise;
168     }
169 
170     @Override
171     public void addListener(Listener listener) {
172         listeners.add(listener);
173     }
174 
175     @Override
176     public void removeListener(Listener listener) {
177         listeners.remove(listener);
178     }
179 
180     @Override
181     public boolean isServer() {
182         return localEndpoint.isServer();
183     }
184 
185     @Override
186     public Http2Stream connectionStream() {
187         return connectionStream;
188     }
189 
190     @Override
191     public Http2Stream stream(int streamId) {
192         return streamMap.get(streamId);
193     }
194 
195     @Override
196     public boolean streamMayHaveExisted(int streamId) {
197         return remoteEndpoint.mayHaveCreatedStream(streamId) || localEndpoint.mayHaveCreatedStream(streamId);
198     }
199 
200     @Override
201     public int numActiveStreams() {
202         return activeStreams.size();
203     }
204 
205     @Override
206     public Http2Stream forEachActiveStream(Http2StreamVisitor visitor) throws Http2Exception {
207         return activeStreams.forEachActiveStream(visitor);
208     }
209 
210     @Override
211     public Endpoint<Http2LocalFlowController> local() {
212         return localEndpoint;
213     }
214 
215     @Override
216     public Endpoint<Http2RemoteFlowController> remote() {
217         return remoteEndpoint;
218     }
219 
220     @Override
221     public boolean goAwayReceived() {
222         return localEndpoint.lastStreamKnownByPeer >= 0;
223     }
224 
225     @Override
226     public void goAwayReceived(final int lastKnownStream, long errorCode, ByteBuf debugData) throws Http2Exception {
227         if (localEndpoint.lastStreamKnownByPeer() >= 0 && localEndpoint.lastStreamKnownByPeer() < lastKnownStream) {
228             throw connectionError(PROTOCOL_ERROR, "lastStreamId MUST NOT increase. Current value: %d new value: %d",
229                     localEndpoint.lastStreamKnownByPeer(), lastKnownStream);
230         }
231 
232         localEndpoint.lastStreamKnownByPeer(lastKnownStream);
233         for (int i = 0; i < listeners.size(); ++i) {
234             try {
235                 listeners.get(i).onGoAwayReceived(lastKnownStream, errorCode, debugData);
236             } catch (Throwable cause) {
237                 logger.error("Caught Throwable from listener onGoAwayReceived.", cause);
238             }
239         }
240 
241         closeStreamsGreaterThanLastKnownStreamId(lastKnownStream, localEndpoint);
242     }
243 
244     @Override
245     public boolean goAwaySent() {
246         return remoteEndpoint.lastStreamKnownByPeer >= 0;
247     }
248 
249     @Override
250     public boolean goAwaySent(final int lastKnownStream, long errorCode, ByteBuf debugData) throws Http2Exception {
251         if (remoteEndpoint.lastStreamKnownByPeer() >= 0) {
252             // Protect against re-entrancy. Could happen if writing the frame fails, and error handling
253             // treating this is a connection handler and doing a graceful shutdown...
254             if (lastKnownStream == remoteEndpoint.lastStreamKnownByPeer()) {
255                 return false;
256             }
257             if (lastKnownStream > remoteEndpoint.lastStreamKnownByPeer()) {
258                 throw connectionError(PROTOCOL_ERROR, "Last stream identifier must not increase between " +
259                                 "sending multiple GOAWAY frames (was '%d', is '%d').",
260                         remoteEndpoint.lastStreamKnownByPeer(), lastKnownStream);
261             }
262         }
263 
264         remoteEndpoint.lastStreamKnownByPeer(lastKnownStream);
265         for (int i = 0; i < listeners.size(); ++i) {
266             try {
267                 listeners.get(i).onGoAwaySent(lastKnownStream, errorCode, debugData);
268             } catch (Throwable cause) {
269                 logger.error("Caught Throwable from listener onGoAwaySent.", cause);
270             }
271         }
272 
273         closeStreamsGreaterThanLastKnownStreamId(lastKnownStream, remoteEndpoint);
274         return true;
275     }
276 
277     private void closeStreamsGreaterThanLastKnownStreamId(final int lastKnownStream,
278                                                           final DefaultEndpoint<?> endpoint) throws Http2Exception {
279         forEachActiveStream(new Http2StreamVisitor() {
280             @Override
281             public boolean visit(Http2Stream stream) {
282                 if (stream.id() > lastKnownStream && endpoint.isValidStreamId(stream.id())) {
283                     stream.close();
284                 }
285                 return true;
286             }
287         });
288     }
289 
290     /**
291      * Determine if {@link #streamMap} only contains the connection stream.
292      */
293     private boolean isStreamMapEmpty() {
294         return streamMap.size() == 1;
295     }
296 
297     /**
298      * Remove a stream from the {@link #streamMap}.
299      * @param stream the stream to remove.
300      * @param itr an iterator that may be pointing to the stream during iteration and {@link Iterator#remove()} will be
301      * used if non-{@code null}.
302      */
303     void removeStream(DefaultStream stream, Iterator<?> itr) {
304         final boolean removed;
305         if (itr == null) {
306             removed = streamMap.remove(stream.id()) != null;
307         } else {
308             itr.remove();
309             removed = true;
310         }
311 
312         if (removed) {
313             for (int i = 0; i < listeners.size(); i++) {
314                 try {
315                     listeners.get(i).onStreamRemoved(stream);
316                 } catch (Throwable cause) {
317                     logger.error("Caught Throwable from listener onStreamRemoved.", cause);
318                 }
319             }
320 
321             if (closePromise != null && isStreamMapEmpty()) {
322                 closePromise.trySuccess(null);
323             }
324         }
325     }
326 
327     static State activeState(int streamId, State initialState, boolean isLocal, boolean halfClosed)
328             throws Http2Exception {
329         switch (initialState) {
330         case IDLE:
331             return halfClosed ? isLocal ? HALF_CLOSED_LOCAL : HALF_CLOSED_REMOTE : OPEN;
332         case RESERVED_LOCAL:
333             return HALF_CLOSED_REMOTE;
334         case RESERVED_REMOTE:
335             return HALF_CLOSED_LOCAL;
336         default:
337             throw streamError(streamId, PROTOCOL_ERROR, "Attempting to open a stream in an invalid state: "
338                     + initialState);
339         }
340     }
341 
342     void notifyHalfClosed(Http2Stream stream) {
343         for (int i = 0; i < listeners.size(); i++) {
344             try {
345                 listeners.get(i).onStreamHalfClosed(stream);
346             } catch (Throwable cause) {
347                 logger.error("Caught Throwable from listener onStreamHalfClosed.", cause);
348             }
349         }
350     }
351 
352     void notifyClosed(Http2Stream stream) {
353         for (int i = 0; i < listeners.size(); i++) {
354             try {
355                 listeners.get(i).onStreamClosed(stream);
356             } catch (Throwable cause) {
357                 logger.error("Caught Throwable from listener onStreamClosed.", cause);
358             }
359         }
360     }
361 
362     @Override
363     public PropertyKey newKey() {
364         return propertyKeyRegistry.newKey();
365     }
366 
367     /**
368      * Verifies that the key was created by this connection.
369      *
370      * @throws IllegalArgumentException if the key was not created by this connection.
371      */
372     final DefaultPropertyKey verifyKey(DefaultPropertyKey key) {
373         return key.verifyConnection(this);
374     }
375 
376     /**
377      * Simple stream implementation. Streams can be compared to each other by priority.
378      */
379     private class DefaultStream implements Http2Stream {
380         private static final byte META_STATE_SENT_RST = 1;
381         private static final byte META_STATE_SENT_HEADERS = 1 << 1;
382         private static final byte META_STATE_SENT_TRAILERS = 1 << 2;
383         private static final byte META_STATE_SENT_PUSHPROMISE = 1 << 3;
384         private static final byte META_STATE_RECV_HEADERS = 1 << 4;
385         private static final byte META_STATE_RECV_TRAILERS = 1 << 5;
386         private final int id;
387         private final long identity;
388         private final PropertyMap properties = new PropertyMap();
389         private State state;
390         private byte metaState;
391 
392         DefaultStream(long identity, int id, State state) {
393             this.identity = identity;
394             this.id = id;
395             this.state = state;
396         }
397 
398         @Override
399         public final int id() {
400             return id;
401         }
402 
403         @Override
404         public final State state() {
405             return state;
406         }
407 
408         @Override
409         public boolean isResetSent() {
410             return (metaState & META_STATE_SENT_RST) != 0;
411         }
412 
413         @Override
414         public Http2Stream resetSent() {
415             metaState |= META_STATE_SENT_RST;
416             return this;
417         }
418 
419         @Override
420         public Http2Stream headersSent(boolean isInformational) {
421             if (!isInformational) {
422                 metaState |= isHeadersSent() ? META_STATE_SENT_TRAILERS : META_STATE_SENT_HEADERS;
423             }
424             return this;
425         }
426 
427         @Override
428         public boolean isHeadersSent() {
429             return (metaState & META_STATE_SENT_HEADERS) != 0;
430         }
431 
432         @Override
433         public boolean isTrailersSent() {
434             return (metaState & META_STATE_SENT_TRAILERS) != 0;
435         }
436 
437         @Override
438         public Http2Stream headersReceived(boolean isInformational) {
439             if (!isInformational) {
440                 metaState |= isHeadersReceived() ? META_STATE_RECV_TRAILERS : META_STATE_RECV_HEADERS;
441             }
442             return this;
443         }
444 
445         @Override
446         public boolean isHeadersReceived() {
447             return (metaState & META_STATE_RECV_HEADERS) != 0;
448         }
449 
450         @Override
451         public boolean isTrailersReceived() {
452             return (metaState & META_STATE_RECV_TRAILERS) != 0;
453         }
454 
455         @Override
456         public Http2Stream pushPromiseSent() {
457             metaState |= META_STATE_SENT_PUSHPROMISE;
458             return this;
459         }
460 
461         @Override
462         public boolean isPushPromiseSent() {
463             return (metaState & META_STATE_SENT_PUSHPROMISE) != 0;
464         }
465 
466         @Override
467         public final <V> V setProperty(PropertyKey key, V value) {
468             checkNotNull(key, "key");
469             if (key instanceof DefaultPropertyKey) {
470                 return properties.add(verifyKey((DefaultPropertyKey) key), value);
471             }
472             if (key instanceof Http2ConnectionPropertyKeys.GlobalPropertyKey) {
473                 return properties.addGlobal((Http2ConnectionPropertyKeys.GlobalPropertyKey) key, value);
474             }
475             throw new IllegalArgumentException("Unsupported key: " + key);
476         }
477 
478         @Override
479         public final <V> V getProperty(PropertyKey key) {
480             checkNotNull(key, "key");
481             if (key instanceof DefaultPropertyKey) {
482                 return properties.get(verifyKey((DefaultPropertyKey) key));
483             }
484             if (key instanceof Http2ConnectionPropertyKeys.GlobalPropertyKey) {
485                 return properties.getGlobal((Http2ConnectionPropertyKeys.GlobalPropertyKey) key);
486             }
487             throw new IllegalArgumentException("Unsupported key: " + key);
488         }
489 
490         @Override
491         public final <V> V removeProperty(PropertyKey key) {
492             checkNotNull(key, "key");
493             if (key instanceof DefaultPropertyKey) {
494                 return properties.remove(verifyKey((DefaultPropertyKey) key));
495             }
496             if (key instanceof Http2ConnectionPropertyKeys.GlobalPropertyKey) {
497                 return properties.removeGlobal((Http2ConnectionPropertyKeys.GlobalPropertyKey) key);
498             }
499             throw new IllegalArgumentException("Unsupported key: " + key);
500         }
501 
502         @Override
503         public Http2Stream open(boolean halfClosed) throws Http2Exception {
504             state = activeState(id, state, isLocal(), halfClosed);
505             final DefaultEndpoint<? extends Http2FlowController> endpoint = createdBy();
506             if (!endpoint.canOpenStream()) {
507                 throw connectionError(PROTOCOL_ERROR, "Maximum active streams violated for this endpoint: " +
508                         endpoint.maxActiveStreams());
509             }
510 
511             activate();
512             return this;
513         }
514 
515         void activate() {
516             // If the stream is opened in a half-closed state, the headers must have either
517             // been sent if this is a local stream, or received if it is a remote stream.
518             if (state == HALF_CLOSED_LOCAL) {
519                 headersSent(/*isInformational*/ false);
520             } else if (state == HALF_CLOSED_REMOTE) {
521                 headersReceived(/*isInformational*/ false);
522             }
523             activeStreams.activate(this);
524         }
525 
526         Http2Stream close(Iterator<?> itr) {
527             if (state == CLOSED) {
528                 return this;
529             }
530 
531             state = CLOSED;
532 
533             --createdBy().numStreams;
534             activeStreams.deactivate(this, itr);
535             return this;
536         }
537 
538         @Override
539         public Http2Stream close() {
540             return close(null);
541         }
542 
543         @Override
544         public Http2Stream closeLocalSide() {
545             switch (state) {
546             case OPEN:
547                 state = HALF_CLOSED_LOCAL;
548                 notifyHalfClosed(this);
549                 break;
550             case HALF_CLOSED_LOCAL:
551                 break;
552             default:
553                 close();
554                 break;
555             }
556             return this;
557         }
558 
559         @Override
560         public Http2Stream closeRemoteSide() {
561             switch (state) {
562             case OPEN:
563                 state = HALF_CLOSED_REMOTE;
564                 notifyHalfClosed(this);
565                 break;
566             case HALF_CLOSED_REMOTE:
567                 break;
568             default:
569                 close();
570                 break;
571             }
572             return this;
573         }
574 
575         DefaultEndpoint<? extends Http2FlowController> createdBy() {
576             return localEndpoint.isValidStreamId(id) ? localEndpoint : remoteEndpoint;
577         }
578 
579         final boolean isLocal() {
580             return localEndpoint.isValidStreamId(id);
581         }
582 
583         /**
584          * Provides the lazy initialization for the {@link DefaultStream} data map.
585          */
586         private class PropertyMap {
587             Object[] values = EmptyArrays.EMPTY_OBJECTS;
588 
589             /**
590              * Storage for keys that are not tied to this connection. Allocated lazily as such keys are expected to
591              * be used rarely, if ever, compared to keys created via {@link Http2Connection#newKey()}.
592              */
593             Object[] globalValues = EmptyArrays.EMPTY_OBJECTS;
594 
595             <V> V add(DefaultPropertyKey key, V value) {
596                 resizeIfNecessary(key.index);
597                 @SuppressWarnings("unchecked")
598                 V prevValue = (V) values[key.index];
599                 values[key.index] = value;
600                 return prevValue;
601             }
602 
603             @SuppressWarnings("unchecked")
604             <V> V get(DefaultPropertyKey key) {
605                 if (key.index >= values.length) {
606                     return null;
607                 }
608                 return (V) values[key.index];
609             }
610 
611             @SuppressWarnings("unchecked")
612             <V> V remove(DefaultPropertyKey key) {
613                 V prevValue = null;
614                 if (key.index < values.length) {
615                     prevValue = (V) values[key.index];
616                     values[key.index] = null;
617                 }
618                 return prevValue;
619             }
620 
621             void resizeIfNecessary(int index) {
622                 if (index >= values.length) {
623                     values = Arrays.copyOf(values, propertyKeyRegistry.size());
624                 }
625             }
626 
627             <V> V addGlobal(Http2ConnectionPropertyKeys.GlobalPropertyKey key, V value) {
628                 resizeGlobalIfNecessary(key.index);
629                 @SuppressWarnings("unchecked")
630                 V prevValue = (V) globalValues[key.index];
631                 globalValues[key.index] = value;
632                 return prevValue;
633             }
634 
635             @SuppressWarnings("unchecked")
636             <V> V getGlobal(Http2ConnectionPropertyKeys.GlobalPropertyKey key) {
637                 if (key.index >= globalValues.length) {
638                     return null;
639                 }
640                 return (V) globalValues[key.index];
641             }
642 
643             @SuppressWarnings("unchecked")
644             <V> V removeGlobal(Http2ConnectionPropertyKeys.GlobalPropertyKey key) {
645                 V prevValue = null;
646                 if (key.index < globalValues.length) {
647                     prevValue = (V) globalValues[key.index];
648                     globalValues[key.index] = null;
649                 }
650                 return prevValue;
651             }
652 
653             void resizeGlobalIfNecessary(int index) {
654                 if (index >= globalValues.length) {
655                     globalValues = Arrays.copyOf(globalValues, Http2ConnectionPropertyKeys.keyCount());
656                 }
657             }
658         }
659 
660         @Override
661         public boolean equals(final Object obj) {
662             return super.equals(obj);
663         }
664 
665         @Override
666         public int hashCode() {
667             long value = identity;
668             if (value == 0) {
669                 return System.identityHashCode(this);
670             }
671             return (int) (value ^ (value >>> 32));
672         }
673 
674         @Override
675         public String toString() {
676             return getClass().getSimpleName() +
677                     "{id=" + id +
678                     ", state=" + state +
679                     ", metaState=" + metaState +
680                     '}';
681         }
682     }
683 
684     /**
685      * Stream class representing the connection, itself.
686      */
687     private final class ConnectionStream extends DefaultStream {
688         ConnectionStream() {
689             super(0, CONNECTION_STREAM_ID, IDLE);
690         }
691 
692         @Override
693         public boolean isResetSent() {
694             return false;
695         }
696 
697         @Override
698         DefaultEndpoint<? extends Http2FlowController> createdBy() {
699             return null;
700         }
701 
702         @Override
703         public Http2Stream resetSent() {
704             throw new UnsupportedOperationException();
705         }
706 
707         @Override
708         public Http2Stream open(boolean halfClosed) {
709             throw new UnsupportedOperationException();
710         }
711 
712         @Override
713         public Http2Stream close() {
714             throw new UnsupportedOperationException();
715         }
716 
717         @Override
718         public Http2Stream closeLocalSide() {
719             throw new UnsupportedOperationException();
720         }
721 
722         @Override
723         public Http2Stream closeRemoteSide() {
724             throw new UnsupportedOperationException();
725         }
726 
727         @Override
728         public Http2Stream headersSent(boolean isInformational) {
729             throw new UnsupportedOperationException();
730         }
731 
732         @Override
733         public boolean isHeadersSent() {
734             throw new UnsupportedOperationException();
735         }
736 
737         @Override
738         public Http2Stream pushPromiseSent() {
739             throw new UnsupportedOperationException();
740         }
741 
742         @Override
743         public boolean isPushPromiseSent() {
744             throw new UnsupportedOperationException();
745         }
746     }
747 
748     /**
749      * Simple endpoint implementation.
750      */
751     private final class DefaultEndpoint<F extends Http2FlowController> implements Endpoint<F> {
752         private final boolean server;
753         /**
754          * This is an always increasing sequence number used to hash {@link DefaultStream} instances.
755          */
756         private long lastCreatedStreamIdentity;
757         /**
758          * The minimum stream ID allowed when creating the next stream. This only applies at the time the stream is
759          * created. If the ID of the stream being created is less than this value, stream creation will fail. Upon
760          * successful creation of a stream, this value is incremented to the next valid stream ID.
761          */
762         private int nextStreamIdToCreate;
763         /**
764          * Used for reservation of stream IDs. Stream IDs can be reserved in advance by applications before the streams
765          * are actually created.  For example, applications may choose to buffer stream creation attempts as a way of
766          * working around {@code SETTINGS_MAX_CONCURRENT_STREAMS}, in which case they will reserve stream IDs for each
767          * buffered stream.
768          */
769         private int nextReservationStreamId;
770         private int lastStreamKnownByPeer = -1;
771         private boolean pushToAllowed;
772         private F flowController;
773         private int maxStreams;
774         private int maxActiveStreams;
775         private final int maxReservedStreams;
776         // Fields accessed by inner classes
777         int numActiveStreams;
778         int numStreams;
779 
780         DefaultEndpoint(boolean server, int maxReservedStreams) {
781             this.lastCreatedStreamIdentity = 0;
782             this.server = server;
783 
784             // Determine the starting stream ID for this endpoint. Client-initiated streams
785             // are odd and server-initiated streams are even. Zero is reserved for the
786             // connection. Stream 1 is reserved client-initiated stream for responding to an
787             // upgrade from HTTP 1.1.
788             if (server) {
789                 nextStreamIdToCreate = 2;
790                 nextReservationStreamId = 0;
791             } else {
792                 nextStreamIdToCreate = 1;
793                 // For manually created client-side streams, 1 is reserved for HTTP upgrade, so start at 3.
794                 nextReservationStreamId = 1;
795             }
796 
797             // Push is disallowed by default for servers and allowed for clients.
798             pushToAllowed = !server;
799             maxActiveStreams = MAX_VALUE;
800             this.maxReservedStreams = checkPositiveOrZero(maxReservedStreams, "maxReservedStreams");
801             updateMaxStreams();
802         }
803 
804         @Override
805         public int incrementAndGetNextStreamId() {
806             return nextReservationStreamId >= 0 ? nextReservationStreamId += 2 : nextReservationStreamId;
807         }
808 
809         private void incrementExpectedStreamId(int streamId) {
810             if (streamId > nextReservationStreamId && nextReservationStreamId >= 0) {
811                 nextReservationStreamId = streamId;
812             }
813             nextStreamIdToCreate = streamId + 2;
814             ++numStreams;
815         }
816 
817         @Override
818         public boolean isValidStreamId(int streamId) {
819             return streamId > 0 && server == ((streamId & 1) == 0);
820         }
821 
822         @Override
823         public boolean mayHaveCreatedStream(int streamId) {
824             return isValidStreamId(streamId) && streamId <= lastStreamCreated();
825         }
826 
827         @Override
828         public boolean canOpenStream() {
829             return numActiveStreams < maxActiveStreams;
830         }
831 
832         @Override
833         public DefaultStream createStream(int streamId, boolean halfClosed) throws Http2Exception {
834             State state = activeState(streamId, IDLE, isLocal(), halfClosed);
835 
836             checkNewStreamAllowed(streamId, state);
837 
838             lastCreatedStreamIdentity++;
839 
840             // Create and initialize the stream.
841             DefaultStream stream = new DefaultStream(lastCreatedStreamIdentity, streamId, state);
842 
843             incrementExpectedStreamId(streamId);
844 
845             addStream(stream);
846 
847             stream.activate();
848             return stream;
849         }
850 
851         @Override
852         public boolean created(Http2Stream stream) {
853             return stream instanceof DefaultStream && ((DefaultStream) stream).createdBy() == this;
854         }
855 
856         @Override
857         public boolean isServer() {
858             return server;
859         }
860 
861         @Override
862         public DefaultStream reservePushStream(int streamId, Http2Stream parent) throws Http2Exception {
863             if (parent == null) {
864                 throw connectionError(PROTOCOL_ERROR, "Parent stream missing");
865             }
866             if (isLocal() ? !parent.state().localSideOpen() : !parent.state().remoteSideOpen()) {
867                 throw connectionError(PROTOCOL_ERROR, "Stream %d is not open for sending push promise", parent.id());
868             }
869             if (!opposite().allowPushTo()) {
870                 throw connectionError(PROTOCOL_ERROR, "Server push not allowed to opposite endpoint");
871             }
872             State state = isLocal() ? RESERVED_LOCAL : RESERVED_REMOTE;
873             checkNewStreamAllowed(streamId, state);
874 
875             lastCreatedStreamIdentity++;
876 
877             // Create and initialize the stream.
878             DefaultStream stream = new DefaultStream(lastCreatedStreamIdentity, streamId, state);
879 
880             incrementExpectedStreamId(streamId);
881 
882             // Register the stream.
883             addStream(stream);
884             return stream;
885         }
886 
887         private void addStream(DefaultStream stream) {
888             // Add the stream to the map and priority tree.
889             streamMap.put(stream.id(), stream);
890 
891             // Notify the listeners of the event.
892             for (int i = 0; i < listeners.size(); i++) {
893                 try {
894                     listeners.get(i).onStreamAdded(stream);
895                 } catch (Throwable cause) {
896                     logger.error("Caught Throwable from listener onStreamAdded.", cause);
897                 }
898             }
899         }
900 
901         @Override
902         public void allowPushTo(boolean allow) {
903             if (allow && server) {
904                 throw new IllegalArgumentException("Servers do not allow push");
905             }
906             pushToAllowed = allow;
907         }
908 
909         @Override
910         public boolean allowPushTo() {
911             return pushToAllowed;
912         }
913 
914         @Override
915         public int numActiveStreams() {
916             return numActiveStreams;
917         }
918 
919         @Override
920         public int maxActiveStreams() {
921             return maxActiveStreams;
922         }
923 
924         @Override
925         public void maxActiveStreams(int maxActiveStreams) {
926             this.maxActiveStreams = maxActiveStreams;
927             updateMaxStreams();
928         }
929 
930         @Override
931         public int lastStreamCreated() {
932             // Stream ids are always incremented by 2 so just subtract it. This is even ok in the case
933             // of nextStreamIdToCreate overflown as it will just return the correct positive number.
934             // Use max(...) to ensure we return the correct value for the case when its a client and no stream
935             // was created yet.
936             return Math.max(0, nextStreamIdToCreate - 2);
937         }
938 
939         @Override
940         public int lastStreamKnownByPeer() {
941             return lastStreamKnownByPeer;
942         }
943 
944         private void lastStreamKnownByPeer(int lastKnownStream) {
945             lastStreamKnownByPeer = lastKnownStream;
946         }
947 
948         @Override
949         public F flowController() {
950             return flowController;
951         }
952 
953         @Override
954         public void flowController(F flowController) {
955             this.flowController = checkNotNull(flowController, "flowController");
956         }
957 
958         @Override
959         public Endpoint<? extends Http2FlowController> opposite() {
960             return isLocal() ? remoteEndpoint : localEndpoint;
961         }
962 
963         private void updateMaxStreams() {
964             maxStreams = (int) Math.min(MAX_VALUE, (long) maxActiveStreams + maxReservedStreams);
965         }
966 
967         private void checkNewStreamAllowed(int streamId, State state) throws Http2Exception {
968             assert state != IDLE;
969             if (lastStreamKnownByPeer >= 0 && streamId > lastStreamKnownByPeer) {
970                 throw streamError(streamId, REFUSED_STREAM,
971                         "Cannot create stream %d greater than Last-Stream-ID %d from GOAWAY.",
972                         streamId, lastStreamKnownByPeer);
973             }
974             if (!isValidStreamId(streamId)) {
975                 if (streamId < 0) {
976                     throw new Http2NoMoreStreamIdsException();
977                 }
978                 throw connectionError(PROTOCOL_ERROR, "Request stream %d is not correct for %s connection", streamId,
979                         server ? "server" : "client");
980             }
981             // This check must be after all id validated checks, but before the max streams check because it may be
982             // recoverable to some degree for handling frames which can be sent on closed streams.
983             if (streamId < nextStreamIdToCreate) {
984                 throw closedStreamError(PROTOCOL_ERROR, "Request stream %d is behind the next expected stream %d",
985                         streamId, nextStreamIdToCreate);
986             }
987             if (nextStreamIdToCreate <= 0) {
988                 // We exhausted the stream id space that we can use. Let's signal this back but also signal that
989                 // we still may want to process active streams.
990                 throw new Http2Exception(REFUSED_STREAM, "Stream IDs are exhausted for this endpoint.",
991                         Http2Exception.ShutdownHint.GRACEFUL_SHUTDOWN);
992             }
993             boolean isReserved = state == RESERVED_LOCAL || state == RESERVED_REMOTE;
994             if (!isReserved && !canOpenStream() || isReserved && numStreams >= maxStreams) {
995                 throw streamError(streamId, REFUSED_STREAM, "Maximum active streams violated for this endpoint: " +
996                         (isReserved ? maxStreams : maxActiveStreams));
997             }
998             if (isClosed()) {
999                 throw connectionError(INTERNAL_ERROR, "Attempted to create stream id %d after connection was closed",
1000                                       streamId);
1001             }
1002         }
1003 
1004         private boolean isLocal() {
1005             return this == localEndpoint;
1006         }
1007     }
1008 
1009     /**
1010      * Allows events which would modify the collection of active streams to be queued while iterating via {@link
1011      * #forEachActiveStream(Http2StreamVisitor)}.
1012      */
1013     interface Event {
1014         /**
1015          * Trigger the original intention of this event. Expect to modify the active streams list.
1016          * <p/>
1017          * If a {@link RuntimeException} object is thrown it will be logged and <strong>not propagated</strong>.
1018          * Throwing from this method is not supported and is considered a programming error.
1019          */
1020         void process();
1021     }
1022 
1023     /**
1024      * Manages the list of currently active streams.  Queues any {@link Event}s that would modify the list of
1025      * active streams in order to prevent modification while iterating.
1026      */
1027     private final class ActiveStreams {
1028         private final List<Listener> listeners;
1029         private final Queue<Event> pendingEvents = new ArrayDeque<Event>(4);
1030         private final Set<Http2Stream> streams = new LinkedHashSet<Http2Stream>();
1031         private int pendingIterations;
1032 
1033         ActiveStreams(List<Listener> listeners) {
1034             this.listeners = listeners;
1035         }
1036 
1037         public int size() {
1038             return streams.size();
1039         }
1040 
1041         public void activate(final DefaultStream stream) {
1042             if (allowModifications()) {
1043                 addToActiveStreams(stream);
1044             } else {
1045                 pendingEvents.add(new Event() {
1046                     @Override
1047                     public void process() {
1048                         addToActiveStreams(stream);
1049                     }
1050                 });
1051             }
1052         }
1053 
1054         public void deactivate(final DefaultStream stream, final Iterator<?> itr) {
1055             if (allowModifications() || itr != null) {
1056                 removeFromActiveStreams(stream, itr);
1057             } else {
1058                 pendingEvents.add(new Event() {
1059                     @Override
1060                     public void process() {
1061                         removeFromActiveStreams(stream, itr);
1062                     }
1063                 });
1064             }
1065         }
1066 
1067         public Http2Stream forEachActiveStream(Http2StreamVisitor visitor) throws Http2Exception {
1068             incrementPendingIterations();
1069             try {
1070                 for (Http2Stream stream : streams) {
1071                     if (!visitor.visit(stream)) {
1072                         return stream;
1073                     }
1074                 }
1075                 return null;
1076             } finally {
1077                 decrementPendingIterations();
1078             }
1079         }
1080 
1081         void addToActiveStreams(DefaultStream stream) {
1082             if (streams.add(stream)) {
1083                 // Update the number of active streams initiated by the endpoint.
1084                 stream.createdBy().numActiveStreams++;
1085 
1086                 for (int i = 0; i < listeners.size(); i++) {
1087                     try {
1088                         listeners.get(i).onStreamActive(stream);
1089                     } catch (Throwable cause) {
1090                         logger.error("Caught Throwable from listener onStreamActive.", cause);
1091                     }
1092                 }
1093             }
1094         }
1095 
1096         void removeFromActiveStreams(DefaultStream stream, Iterator<?> itr) {
1097             if (streams.remove(stream)) {
1098                 // Update the number of active streams initiated by the endpoint.
1099                 stream.createdBy().numActiveStreams--;
1100                 notifyClosed(stream);
1101             }
1102             removeStream(stream, itr);
1103         }
1104 
1105         boolean allowModifications() {
1106             return pendingIterations == 0;
1107         }
1108 
1109         void incrementPendingIterations() {
1110             ++pendingIterations;
1111         }
1112 
1113         void decrementPendingIterations() {
1114             --pendingIterations;
1115             if (allowModifications()) {
1116                 for (;;) {
1117                     Event event = pendingEvents.poll();
1118                     if (event == null) {
1119                         break;
1120                     }
1121                     try {
1122                         event.process();
1123                     } catch (Throwable cause) {
1124                         logger.error("Caught Throwable while processing pending ActiveStreams$Event.", cause);
1125                     }
1126                 }
1127             }
1128         }
1129     }
1130 
1131     /**
1132      * Implementation of {@link PropertyKey} that specifies the index position of the property.
1133      */
1134     final class DefaultPropertyKey implements PropertyKey {
1135         final int index;
1136 
1137         DefaultPropertyKey(int index) {
1138             this.index = index;
1139         }
1140 
1141         DefaultPropertyKey verifyConnection(Http2Connection connection) {
1142             if (connection != DefaultHttp2Connection.this) {
1143                 throw new IllegalArgumentException("Using a key that was not created by this connection");
1144             }
1145             return this;
1146         }
1147     }
1148 
1149     /**
1150      * A registry of all stream property keys known by this connection.
1151      */
1152     private final class PropertyKeyRegistry {
1153         /**
1154          * Initial size of 4 because the default configuration currently has 3 listeners
1155          * (local/remote flow controller and {@link StreamByteDistributor}) and we leave room for 1 extra.
1156          * We could be more aggressive but the ArrayList resize will double the size if we are too small.
1157          */
1158         final List<DefaultPropertyKey> keys = new ArrayList<DefaultPropertyKey>(4);
1159 
1160         /**
1161          * Registers a new property key.
1162          */
1163         DefaultPropertyKey newKey() {
1164             DefaultPropertyKey key = new DefaultPropertyKey(keys.size());
1165             keys.add(key);
1166             return key;
1167         }
1168 
1169         int size() {
1170             return keys.size();
1171         }
1172     }
1173 }