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