1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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
46
47
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
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
99
100
101
102
103
104
105
106
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
190
191
192
193
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 }