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