View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  package org.apache.hc.core5.http2.impl.nio;
28  
29  import java.io.IOException;
30  import java.nio.ByteBuffer;
31  import java.nio.channels.ReadableByteChannel;
32  
33  import org.apache.hc.core5.http.ConnectionClosedException;
34  import org.apache.hc.core5.http2.H2ConnectionException;
35  import org.apache.hc.core5.http2.H2CorruptFrameException;
36  import org.apache.hc.core5.http2.H2Error;
37  import org.apache.hc.core5.http2.H2TransportMetrics;
38  import org.apache.hc.core5.http2.frame.FrameConsts;
39  import org.apache.hc.core5.http2.frame.FrameFlag;
40  import org.apache.hc.core5.http2.frame.RawFrame;
41  import org.apache.hc.core5.http2.impl.BasicH2TransportMetrics;
42  import org.apache.hc.core5.util.Args;
43  
44  /**
45   * Frame input buffer for HTTP/2 non-blocking connections.
46   *
47   * @since 5.0
48   */
49  public final class FrameInputBuffer {
50  
51      enum State { HEAD_EXPECTED, PAYLOAD_EXPECTED }
52  
53      private final BasicH2TransportMetrics metrics;
54      private final int maxFramePayloadSize;
55      private final byte[] bytes;
56      private final ByteBuffer buffer;
57  
58      private State state;
59      private int payloadLen;
60      private int type;
61      private int flags;
62      private int streamId;
63  
64      FrameInputBuffer(final BasicH2TransportMetrics metrics, final int bufferLen, final int maxFramePayloadSize) {
65          Args.notNull(metrics, "HTTP2 transport metrics");
66          Args.positive(maxFramePayloadSize, "Maximum payload size");
67          this.metrics = metrics;
68          this.maxFramePayloadSize = Math.max(maxFramePayloadSize, FrameConsts.MIN_FRAME_SIZE);
69          this.bytes = new byte[bufferLen];
70          this.buffer = ByteBuffer.wrap(bytes);
71          this.buffer.flip();
72          this.state = State.HEAD_EXPECTED;
73      }
74  
75      public FrameInputBuffer(final BasicH2TransportMetrics metrics, final int maxFramePayloadSize) {
76          this(metrics, FrameConsts.HEAD_LEN + maxFramePayloadSize, maxFramePayloadSize);
77      }
78  
79      public FrameInputBuffer(final int maxFramePayloadSize) {
80          this(new BasicH2TransportMetrics(), maxFramePayloadSize);
81      }
82  
83      public void put(final ByteBuffer src) {
84          if (buffer.hasRemaining()) {
85              buffer.compact();
86          } else {
87              buffer.clear();
88          }
89          buffer.put(src);
90          buffer.flip();
91      }
92  
93      /**
94       * Attempts to read a complete frame from the given source buffer and the underlying data
95       * channel. The source buffer is consumed first. More data can be read from the channel
96       * if required.
97       *
98       * @param src the source buffer or {@code null} if not available.
99       * @param channel the underlying data channel.
100      *
101      * @return a complete frame or {@code null} a complete frame cannot be read.
102      *
103      * @since 5.1
104      */
105     public RawFrame read(final ByteBuffer src, final ReadableByteChannel channel) throws IOException {
106         for (;;) {
107             if (src != null) {
108                 if (buffer.hasRemaining()) {
109                     buffer.compact();
110                 } else {
111                     buffer.clear();
112                 }
113                 final int remaining = buffer.remaining();
114                 if (remaining >= src.remaining()) {
115                     buffer.put(src);
116                 } else {
117                     final int limit = src.limit();
118                     src.limit(remaining);
119                     buffer.put(src);
120                     src.limit(limit);
121                 }
122                 buffer.flip();
123             }
124             switch (state) {
125                 case HEAD_EXPECTED:
126                     if (buffer.remaining() >= FrameConsts.HEAD_LEN) {
127                         final int lengthAndType = buffer.getInt();
128                         payloadLen = lengthAndType >> 8;
129                         if (payloadLen > maxFramePayloadSize) {
130                             throw new H2ConnectionException(H2Error.FRAME_SIZE_ERROR, "Frame size exceeds maximum");
131                         }
132                         type = lengthAndType & 0xff;
133                         flags = buffer.get();
134                         streamId = Math.abs(buffer.getInt());
135                         state = State.PAYLOAD_EXPECTED;
136                     } else {
137                         break;
138                     }
139                 case PAYLOAD_EXPECTED:
140                     if (buffer.remaining() >= payloadLen) {
141                         if ((flags & FrameFlag.PADDED.getValue()) > 0) {
142                             if (payloadLen == 0) {
143                                 throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "Inconsistent padding");
144                             }
145                             buffer.mark();
146                             final int padding = buffer.get();
147                             if (payloadLen < padding + 1) {
148                                 throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "Inconsistent padding");
149                             }
150                             buffer.reset();
151                         }
152                         final ByteBuffer payload = payloadLen > 0 ? ByteBuffer.wrap(bytes, buffer.position(), payloadLen) : null;
153                         buffer.position(buffer.position() + payloadLen);
154                         state = State.HEAD_EXPECTED;
155                         metrics.incrementFramesTransferred();
156                         return new RawFrame(type, flags, streamId, payload);
157                     }
158             }
159             if (buffer.hasRemaining()) {
160                 buffer.compact();
161             } else {
162                 buffer.clear();
163             }
164             final int bytesRead = channel.read(buffer);
165             buffer.flip();
166             if (bytesRead > 0) {
167                 metrics.incrementBytesTransferred(bytesRead);
168             }
169             if (bytesRead == 0) {
170                 break;
171             } else if (bytesRead < 0) {
172                 if (state != State.HEAD_EXPECTED || buffer.hasRemaining()) {
173                     throw new H2CorruptFrameException("Corrupt or incomplete HTTP2 frame");
174                 } else {
175                     throw new ConnectionClosedException();
176                 }
177             }
178         }
179         return null;
180     }
181 
182     /**
183      * Attempts to read a complete frame from the underlying data channel.
184      *
185      * @param channel the underlying data channel.
186      */
187     public RawFrame read(final ReadableByteChannel channel) throws IOException {
188         return read(null, channel);
189     }
190 
191     public void reset() {
192         buffer.compact();
193         state = State.HEAD_EXPECTED;
194     }
195 
196     public H2TransportMetrics getMetrics() {
197         return metrics;
198     }
199 
200 }