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