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 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
95
96
97
98
99
100
101
102
103
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
187
188
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 }