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      public RawFrame read(final ReadableByteChannel channel) throws IOException {
94          for (;;) {
95              switch (state) {
96                  case HEAD_EXPECTED:
97                      if (buffer.remaining() >= FrameConsts.HEAD_LEN) {
98                          final int lengthAndType = buffer.getInt();
99                          payloadLen = lengthAndType >> 8;
100                         if (payloadLen > maxFramePayloadSize) {
101                             throw new H2ConnectionException(H2Error.FRAME_SIZE_ERROR, "Frame size exceeds maximum");
102                         }
103                         type = lengthAndType & 0xff;
104                         flags = buffer.get();
105                         streamId = Math.abs(buffer.getInt());
106                         state = State.PAYLOAD_EXPECTED;
107                     } else {
108                         break;
109                     }
110                 case PAYLOAD_EXPECTED:
111                     if (buffer.remaining() >= payloadLen) {
112                         if ((flags & FrameFlag.PADDED.getValue()) > 0) {
113                             if (payloadLen == 0) {
114                                 throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "Inconsistent padding");
115                             }
116                             buffer.mark();
117                             final int padding = buffer.get();
118                             if (payloadLen < padding + 1) {
119                                 throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "Inconsistent padding");
120                             }
121                             buffer.reset();
122                         }
123                         final ByteBuffer payload = payloadLen > 0 ? ByteBuffer.wrap(bytes, buffer.position(), payloadLen) : null;
124                         buffer.position(buffer.position() + payloadLen);
125                         state = State.HEAD_EXPECTED;
126                         metrics.incrementFramesTransferred();
127                         return new RawFrame(type, flags, streamId, payload);
128                     }
129             }
130             if (buffer.hasRemaining()) {
131                 buffer.compact();
132             } else {
133                 buffer.clear();
134             }
135             final int bytesRead = channel.read(buffer);
136             buffer.flip();
137             if (bytesRead > 0) {
138                 metrics.incrementBytesTransferred(bytesRead);
139             }
140             if (bytesRead == 0) {
141                 break;
142             } else if (bytesRead < 0) {
143                 if (state != State.HEAD_EXPECTED || buffer.hasRemaining()) {
144                     throw new H2CorruptFrameException("Corrupt or incomplete HTTP2 frame");
145                 } else {
146                     throw new ConnectionClosedException();
147                 }
148             }
149         }
150         return null;
151     }
152 
153     public void reset() {
154         buffer.compact();
155         state = State.HEAD_EXPECTED;
156     }
157 
158     public H2TransportMetrics getMetrics() {
159         return metrics;
160     }
161 
162 }