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
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
191
192
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 }