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 metrcis");
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 }