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      /**
84       * @deprecated Use {@link #read(ByteBuffer, ReadableByteChannel)}.
85       */
86      @Deprecated
87      public void put(final ByteBuffer src) {
88          if (buffer.hasRemaining()) {
89              buffer.compact();
90          } else {
91              buffer.clear();
92          }
93          buffer.put(src);
94          buffer.flip();
95      }
96  
97      /**
98       * Attempts to read a complete frame from the given source buffer and the underlying data
99       * channel. The source buffer is consumed first. More data can be read from the channel
100      * if required.
101      *
102      * @param src the source buffer or {@code null} if not available.
103      * @param channel the underlying data channel.
104      * @return a complete frame or {@code null} a complete frame cannot be read.
105      * @throws IOException in case of an I/O error.
106      * @since 5.1
107      */
108     public RawFrame read(final ByteBuffer src, final ReadableByteChannel channel) throws IOException {
109         for (;;) {
110             if (src != null) {
111                 if (buffer.hasRemaining()) {
112                     buffer.compact();
113                 } else {
114                     buffer.clear();
115                 }
116                 final int remaining = buffer.remaining();
117                 final int n = src.remaining();
118                 if (remaining >= n) {
119                     buffer.put(src);
120                     metrics.incrementBytesTransferred(n);
121                 } else {
122                     final int limit = src.limit();
123                     src.limit(remaining);
124                     buffer.put(src);
125                     src.limit(limit);
126                     metrics.incrementBytesTransferred(remaining);
127                 }
128                 buffer.flip();
129             }
130             switch (state) {
131                 case HEAD_EXPECTED:
132                     if (buffer.remaining() >= FrameConsts.HEAD_LEN) {
133                         final int lengthAndType = buffer.getInt();
134                         payloadLen = lengthAndType >> 8;
135                         if (payloadLen > maxFramePayloadSize) {
136                             throw new H2ConnectionException(H2Error.FRAME_SIZE_ERROR, "Frame size exceeds maximum");
137                         }
138                         type = lengthAndType & 0xff;
139                         flags = buffer.get();
140                         streamId = Math.abs(buffer.getInt());
141                         state = State.PAYLOAD_EXPECTED;
142                     } else {
143                         break;
144                     }
145                 case PAYLOAD_EXPECTED:
146                     if (buffer.remaining() >= payloadLen) {
147                         if ((flags & FrameFlag.PADDED.getValue()) > 0) {
148                             if (payloadLen == 0) {
149                                 throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "Inconsistent padding");
150                             }
151                             buffer.mark();
152                             final int padding = buffer.get();
153                             if (payloadLen < padding + 1) {
154                                 throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "Inconsistent padding");
155                             }
156                             buffer.reset();
157                         }
158                         final ByteBuffer payload = payloadLen > 0 ? ByteBuffer.wrap(bytes, buffer.position(), payloadLen) : null;
159                         buffer.position(buffer.position() + payloadLen);
160                         state = State.HEAD_EXPECTED;
161                         metrics.incrementFramesTransferred();
162                         return new RawFrame(type, flags, streamId, payload);
163                     }
164             }
165             if (buffer.hasRemaining()) {
166                 buffer.compact();
167             } else {
168                 buffer.clear();
169             }
170             final int bytesRead = channel.read(buffer);
171             buffer.flip();
172             if (bytesRead > 0) {
173                 metrics.incrementBytesTransferred(bytesRead);
174             }
175             if (bytesRead == 0) {
176                 break;
177             } else if (bytesRead < 0) {
178                 if (state != State.HEAD_EXPECTED || buffer.hasRemaining()) {
179                     throw new H2CorruptFrameException("Corrupt or incomplete HTTP2 frame");
180                 } else {
181                     throw new ConnectionClosedException();
182                 }
183             }
184         }
185         return null;
186     }
187 
188     /**
189      * Attempts to read a complete frame from the underlying data channel.
190      *
191      * @param channel the underlying data channel.
192      * @return a complete frame or {@code null} a complete frame cannot be read.
193      * @throws IOException in case of an I/O error.
194      */
195     public RawFrame read(final ReadableByteChannel channel) throws IOException {
196         return read(null, channel);
197     }
198 
199     public void reset() {
200         buffer.compact();
201         state = State.HEAD_EXPECTED;
202     }
203 
204     public H2TransportMetrics getMetrics() {
205         return metrics;
206     }
207 
208 }