diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/H2ConnectionException.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/H2ConnectionException.java index 9e695314a5..fa2069db32 100644 --- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/H2ConnectionException.java +++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/H2ConnectionException.java @@ -40,7 +40,7 @@ public class H2ConnectionException extends IOException { private static final long serialVersionUID = -2014204317155428658L; - private final int code; + private final long code; public H2ConnectionException(final H2Error error, final String message) { super(message); @@ -48,12 +48,16 @@ public H2ConnectionException(final H2Error error, final String message) { this.code = error.getCode(); } - public H2ConnectionException(final int code, final String message) { + public H2ConnectionException(final long code, final String message) { super(message); - this.code = code; + this.code = Args.checkRange(code, 0L, 0xffffffffL, "H2 Error code"); + } + + public H2ConnectionException(final int code, final String message) { + this(Integer.toUnsignedLong(code), message); } - public int getCode() { + public long getCode() { return code; } diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/H2Error.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/H2Error.java index 362b2a0e22..63a4a487bb 100644 --- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/H2Error.java +++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/H2Error.java @@ -136,17 +136,17 @@ public enum H2Error { */ HTTP_1_1_REQUIRED (0x0d); - int code; + long code; - H2Error(final int code) { + H2Error(final long code) { this.code = code; } - public int getCode() { + public long getCode() { return code; } - private static final ConcurrentMap MAP_BY_CODE; + private static final ConcurrentMap MAP_BY_CODE; static { MAP_BY_CODE = new ConcurrentHashMap<>(); for (final H2Error error: values()) { @@ -154,8 +154,12 @@ public int getCode() { } } - public static H2Error getByCode(final int code) { + public static H2Error getByCode(final long code) { return MAP_BY_CODE.get(code); } + public static H2Error getByCode(final int code) { + return getByCode(Integer.toUnsignedLong(code)); + } + } diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/H2StreamResetException.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/H2StreamResetException.java index f636bae8d9..a4c45877f0 100644 --- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/H2StreamResetException.java +++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/H2StreamResetException.java @@ -43,7 +43,7 @@ public class H2StreamResetException extends HttpStreamResetException { * @see java.io.Serializable */ private static final long serialVersionUID = 4280996898701236013L; - private final int code; + private final long code; public H2StreamResetException(final H2Error error, final String message) { super(message); @@ -51,12 +51,16 @@ public H2StreamResetException(final H2Error error, final String message) { this.code = error.getCode(); } - public H2StreamResetException(final int code, final String message) { + public H2StreamResetException(final long code, final String message) { super(message); - this.code = code; + this.code = Args.checkRange(code, 0L, 0xffffffffL, "H2 Error code"); + } + + public H2StreamResetException(final int code, final String message) { + this(Integer.toUnsignedLong(code), message); } - public int getCode() { + public long getCode() { return code; } diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/config/H2Config.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/config/H2Config.java index b8a2c376d4..010accc2a8 100644 --- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/config/H2Config.java +++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/config/H2Config.java @@ -43,17 +43,17 @@ public class H2Config { public static final H2Config DEFAULT = custom().build(); public static final H2Config INIT = initial().build(); - private final int headerTableSize; + private final long headerTableSize; private final boolean pushEnabled; - private final int maxConcurrentStreams; + private final long maxConcurrentStreams; private final int initialWindowSize; private final int maxFrameSize; - private final int maxHeaderListSize; + private final long maxHeaderListSize; private final boolean compressionEnabled; private final int maxContinuations; - H2Config(final int headerTableSize, final boolean pushEnabled, final int maxConcurrentStreams, - final int initialWindowSize, final int maxFrameSize, final int maxHeaderListSize, + H2Config(final long headerTableSize, final boolean pushEnabled, final long maxConcurrentStreams, + final int initialWindowSize, final int maxFrameSize, final long maxHeaderListSize, final boolean compressionEnabled, final int maxContinuations) { super(); this.headerTableSize = headerTableSize; @@ -66,7 +66,7 @@ public class H2Config { this.maxContinuations = maxContinuations; } - public int getHeaderTableSize() { + public long getHeaderTableSize() { return headerTableSize; } @@ -74,7 +74,7 @@ public boolean isPushEnabled() { return pushEnabled; } - public int getMaxConcurrentStreams() { + public long getMaxConcurrentStreams() { return maxConcurrentStreams; } @@ -86,7 +86,7 @@ public int getMaxFrameSize() { return maxFrameSize; } - public int getMaxHeaderListSize() { + public long getMaxHeaderListSize() { return maxHeaderListSize; } @@ -147,12 +147,12 @@ public static H2Config.Builder copy(final H2Config config) { public static class Builder { - private int headerTableSize; + private long headerTableSize; private boolean pushEnabled; - private int maxConcurrentStreams; + private long maxConcurrentStreams; private int initialWindowSize; private int maxFrameSize; - private int maxHeaderListSize; + private long maxHeaderListSize; private boolean compressionEnabled; private int maxContinuations; @@ -167,21 +167,29 @@ public static class Builder { this.maxContinuations = 100; } - public Builder setHeaderTableSize(final int headerTableSize) { - this.headerTableSize = headerTableSize; + public Builder setHeaderTableSize(final long headerTableSize) { + this.headerTableSize = Args.checkRange(headerTableSize, 0L, 0xffffffffL, "Header table size"); return this; } + public Builder setHeaderTableSize(final int headerTableSize) { + return setHeaderTableSize((long) headerTableSize); + } + public Builder setPushEnabled(final boolean pushEnabled) { this.pushEnabled = pushEnabled; return this; } - public Builder setMaxConcurrentStreams(final int maxConcurrentStreams) { - this.maxConcurrentStreams = maxConcurrentStreams; + public Builder setMaxConcurrentStreams(final long maxConcurrentStreams) { + this.maxConcurrentStreams = Args.checkRange(maxConcurrentStreams, 0L, 0xffffffffL, "Max concurrent streams"); return this; } + public Builder setMaxConcurrentStreams(final int maxConcurrentStreams) { + return setMaxConcurrentStreams((long) maxConcurrentStreams); + } + public Builder setInitialWindowSize(final int initialWindowSize) { this.initialWindowSize = Args.checkRange(initialWindowSize, 0, Integer.MAX_VALUE, "Initial window size"); return this; @@ -193,11 +201,15 @@ public Builder setMaxFrameSize(final int maxFrameSize) { return this; } - public Builder setMaxHeaderListSize(final int maxHeaderListSize) { - this.maxHeaderListSize = maxHeaderListSize; + public Builder setMaxHeaderListSize(final long maxHeaderListSize) { + this.maxHeaderListSize = Args.checkRange(maxHeaderListSize, 0L, 0xffffffffL, "Max header list size"); return this; } + public Builder setMaxHeaderListSize(final int maxHeaderListSize) { + return setMaxHeaderListSize((long) maxHeaderListSize); + } + public Builder setCompressionEnabled(final boolean compressionEnabled) { this.compressionEnabled = compressionEnabled; return this; diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/config/H2Setting.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/config/H2Setting.java index 5697aab719..d1e447bcbe 100644 --- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/config/H2Setting.java +++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/config/H2Setting.java @@ -36,20 +36,23 @@ public final class H2Setting { private final H2Param param; - private final int value; + private final long value; - public H2Setting(final H2Param param, final int value) { + public H2Setting(final H2Param param, final long value) { Args.notNull(param, "Setting parameter"); - Args.notNegative(value, "Setting value must be a non-negative value"); this.param = param; - this.value = value; + this.value = Args.checkRange(value, 0L, 0xffffffffL, "Setting value"); + } + + public H2Setting(final H2Param param, final int value) { + this(param, (long) value); } public int getCode() { return param.code; } - public int getValue() { + public long getValue() { return value; } diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/frame/FrameFactory.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/frame/FrameFactory.java index 0efe74e732..56e78c728a 100644 --- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/frame/FrameFactory.java +++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/frame/FrameFactory.java @@ -46,7 +46,7 @@ public RawFrame createSettings(final H2Setting... settings) { final ByteBuffer payload = ByteBuffer.allocate(settings.length * 12); for (final H2Setting setting: settings) { payload.putShort((short) setting.getCode()); - payload.putInt(setting.getValue()); + payload.putInt((int) setting.getValue()); } payload.flip(); return new RawFrame(FrameType.SETTINGS.getValue(), 0, 0, payload); @@ -61,14 +61,19 @@ public RawFrame createResetStream(final int streamId, final H2Error error) { return createResetStream(streamId, error.getCode()); } - public RawFrame createResetStream(final int streamId, final int code) { + public RawFrame createResetStream(final int streamId, final long code) { Args.positive(streamId, "Stream id"); + Args.checkRange(code, 0L, 0xffffffffL, "Error code"); final ByteBuffer payload = ByteBuffer.allocate(4); - payload.putInt(code); + payload.putInt((int) code); payload.flip(); return new RawFrame(FrameType.RST_STREAM.getValue(), 0, streamId, payload); } + public RawFrame createResetStream(final int streamId, final int code) { + return createResetStream(streamId, Integer.toUnsignedLong(code)); + } + public RawFrame createPing(final ByteBuffer opaqueData) { Args.notNull(opaqueData, "Opaque data"); Args.check(opaqueData.remaining() == 8, "Opaque data length must be equal 8"); @@ -86,7 +91,7 @@ public RawFrame createGoAway(final int lastStream, final H2Error error, final St final byte[] debugData = message != null ? message.getBytes(StandardCharsets.US_ASCII) : null; final ByteBuffer payload = ByteBuffer.allocate(8 + (debugData != null ? debugData.length : 0)); payload.putInt(lastStream); - payload.putInt(error.getCode()); + payload.putInt((int) error.getCode()); if (debugData != null) { payload.put(debugData); } diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/frame/FramePrinter.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/frame/FramePrinter.java index bf2497f7f8..39d6e063cd 100644 --- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/frame/FramePrinter.java +++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/frame/FramePrinter.java @@ -105,13 +105,13 @@ public void printPayload(final RawFrame frame, final Appendable appendable) thro while (buf.hasRemaining()) { final int code = buf.getShort(); final H2Param param = H2Param.valueOf(code); - final int value = buf.getInt(); + final long value = Integer.toUnsignedLong(buf.getInt()); if (param != null) { appendable.append(param.name()); } else { appendable.append("0x").append(Integer.toHexString(code)); } - appendable.append(": ").append(Integer.toString(value)).append("\r\n"); + appendable.append(": ").append(Long.toString(value)).append("\r\n"); } } else { appendable.append("Invalid\r\n"); @@ -120,12 +120,12 @@ public void printPayload(final RawFrame frame, final Appendable appendable) thro case RST_STREAM: if (buf.remaining() == 4) { appendable.append("Code "); - final int code = buf.getInt(); + final long code = Integer.toUnsignedLong(buf.getInt()); final H2Error error = H2Error.getByCode(code); if (error != null) { appendable.append(error.name()); } else { - appendable.append("0x").append(Integer.toHexString(code)); + appendable.append("0x").append(Long.toHexString(code)); } appendable.append("\r\n"); } else { @@ -137,12 +137,12 @@ public void printPayload(final RawFrame frame, final Appendable appendable) thro final int lastStream = buf.getInt() & 0x7fffffff; appendable.append("Last stream ").append(Integer.toString(lastStream)).append("\r\n"); appendable.append("Code "); - final int code2 = buf.getInt(); + final long code2 = Integer.toUnsignedLong(buf.getInt()); final H2Error error2 = H2Error.getByCode(code2); if (error2 != null) { appendable.append(error2.name()); } else { - appendable.append("0x").append(Integer.toHexString(code2)); + appendable.append("0x").append(Long.toHexString(code2)); } appendable.append("\r\n"); final byte[] tmp = new byte[buf.remaining()]; diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/AbstractH2StreamMultiplexer.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/AbstractH2StreamMultiplexer.java index 96146ce91f..52bd2a87ab 100644 --- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/AbstractH2StreamMultiplexer.java +++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/AbstractH2StreamMultiplexer.java @@ -76,6 +76,7 @@ import org.apache.hc.core5.http2.config.H2Config; import org.apache.hc.core5.http2.config.H2Param; import org.apache.hc.core5.http2.config.H2Setting; +import org.apache.hc.core5.http2.frame.FrameConsts; import org.apache.hc.core5.http2.frame.FrameFactory; import org.apache.hc.core5.http2.frame.FrameFlag; import org.apache.hc.core5.http2.frame.FrameType; @@ -193,6 +194,11 @@ enum SettingsHandshake { READY, TRANSMITTED, ACKED } this.httpProcessor = Args.notNull(httpProcessor, "HTTP processor"); this.streams = new H2Streams(idGenerator); this.localConfig = h2Config != null ? h2Config : H2Config.DEFAULT; + // The HPACK decoder cannot represent limits above Integer.MAX_VALUE + Args.check(this.localConfig.getHeaderTableSize() <= Integer.MAX_VALUE, + "Header table size exceeds HPACK decoder limit"); + Args.check(this.localConfig.getMaxHeaderListSize() <= Integer.MAX_VALUE, + "Max header list size exceeds HPACK decoder limit"); this.inputMetrics = new BasicH2TransportMetrics(); this.outputMetrics = new BasicH2TransportMetrics(); this.connMetrics = new BasicHttpConnectionMetrics(this.inputMetrics, this.outputMetrics); @@ -201,8 +207,8 @@ enum SettingsHandshake { READY, TRANSMITTED, ACKED } this.outputQueue = new ConcurrentLinkedDeque<>(); this.pingHandlers = new ConcurrentLinkedQueue<>(); this.outputRequests = new AtomicInteger(0); - this.hPackEncoder = new HPackEncoder(H2Config.INIT.getHeaderTableSize(), CharCodingSupport.createEncoder(charCodingConfig)); - this.hPackDecoder = new HPackDecoder(H2Config.INIT.getHeaderTableSize(), CharCodingSupport.createDecoder(charCodingConfig)); + this.hPackEncoder = new HPackEncoder((int) H2Config.INIT.getHeaderTableSize(), CharCodingSupport.createEncoder(charCodingConfig)); + this.hPackDecoder = new HPackDecoder((int) H2Config.INIT.getHeaderTableSize(), CharCodingSupport.createDecoder(charCodingConfig)); this.remoteConfig = H2Config.INIT; this.connInputWindow = new AtomicInteger(H2Config.INIT.getInitialWindowSize()); this.connOutputWindow = new AtomicInteger(H2Config.INIT.getInitialWindowSize()); @@ -210,7 +216,7 @@ enum SettingsHandshake { READY, TRANSMITTED, ACKED } this.initInputWinSize = H2Config.INIT.getInitialWindowSize(); this.initOutputWinSize = H2Config.INIT.getInitialWindowSize(); - this.hPackDecoder.setMaxListSize(this.localConfig.getMaxHeaderListSize()); + this.hPackDecoder.setMaxListSize((int) this.localConfig.getMaxHeaderListSize()); this.lowMark = H2Config.INIT.getInitialWindowSize() / 2; this.streamListener = streamListener; this.lastActivityTime = System.currentTimeMillis(); @@ -235,7 +241,8 @@ void submitCommand(final Command command) { ioSession.enqueue(command, Command.Priority.NORMAL); } - abstract void validateSetting(H2Param param, int value) throws H2ConnectionException; + abstract void validateSetting(H2Param param, long value) throws H2ConnectionException; + abstract H2Setting[] generateSettings(H2Config localConfig); @@ -575,7 +582,7 @@ public final void onOutput() throws HttpException, IOException { }))); return; } - while (streams.getLocalCount() < Integer.toUnsignedLong(remoteConfig.getMaxConcurrentStreams())) { + while (streams.getLocalCount() < remoteConfig.getMaxConcurrentStreams()) { final Command command = ioSession.poll(); if (command == null) { break; @@ -951,7 +958,7 @@ private void consumeFrame(final RawFrame frame) throws HttpException, IOExceptio final H2Stream stream = streams.lookupSeen(streamId); if (stream != null) { - final int errorCode = payload.getInt(); + final long errorCode = Integer.toUnsignedLong(payload.getInt()); if (errorCode == H2Error.NO_ERROR.getCode() && allowGracefulAbort(stream)) { stream.abortGracefully(); requestSessionOutput(); @@ -1081,7 +1088,7 @@ private void consumeFrame(final RawFrame frame) throws HttpException, IOExceptio throw new H2ConnectionException(H2Error.FRAME_SIZE_ERROR, "Invalid GOAWAY payload"); } final int processedLocalStreamId = payload.getInt() & 0x7fffffff; - final int errorCode = payload.getInt(); + final long errorCode = Integer.toUnsignedLong(payload.getInt()); goAwayReceived = true; if (errorCode == H2Error.NO_ERROR.getCode()) { if (connState.compareTo(ConnectionHandshake.ACTIVE) <= 0) { @@ -1263,7 +1270,7 @@ private void consumeSettingsFrame(final ByteBuffer payload) throws IOException { final H2Config.Builder configBuilder = H2Config.initial(); while (payload.hasRemaining()) { final int code = payload.getShort(); - final int value = payload.getInt(); + final long value = Integer.toUnsignedLong(payload.getInt()); final H2Param param = H2Param.valueOf(code); if (param != null) { validateSetting(param, value); @@ -1286,19 +1293,22 @@ private void consumeSettingsFrame(final ByteBuffer payload) throws IOException { configBuilder.setPushEnabled(value == 1); break; case INITIAL_WINDOW_SIZE: - if (value < 0) { + if (value > Integer.MAX_VALUE) { throw new H2ConnectionException(H2Error.FLOW_CONTROL_ERROR, - "Invalid initial window size: " + Integer.toUnsignedLong(value)); + "Invalid initial window size: " + value); } try { - configBuilder.setInitialWindowSize(value); + configBuilder.setInitialWindowSize((int) value); } catch (final IllegalArgumentException ex) { throw new H2ConnectionException(H2Error.FLOW_CONTROL_ERROR, ex.getMessage()); } break; case MAX_FRAME_SIZE: + if (value < FrameConsts.MIN_FRAME_SIZE || value > FrameConsts.MAX_FRAME_SIZE) { + throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "Invalid max frame size: " + value); + } try { - configBuilder.setMaxFrameSize(value); + configBuilder.setMaxFrameSize((int) value); } catch (final IllegalArgumentException ex) { throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, ex.getMessage()); } @@ -1341,9 +1351,7 @@ private void applyRemoteSettings(final H2Config config) throws H2ConnectionExcep remoteConfig = config; // The peer's HEADER_TABLE_SIZE is an upper bound for the encoder. Keep the local // dynamic table bounded to limit memory usage and lookup cost per connection. - hPackEncoder.setMaxTableSize((int) Math.min( - Integer.toUnsignedLong(remoteConfig.getHeaderTableSize()), - H2Config.INIT.getHeaderTableSize())); + hPackEncoder.setMaxTableSize((int) Math.min(remoteConfig.getHeaderTableSize(), H2Config.INIT.getHeaderTableSize())); final int delta = remoteConfig.getInitialWindowSize() - initOutputWinSize; initOutputWinSize = remoteConfig.getInitialWindowSize(); final int maxFrameSize = remoteConfig.getMaxFrameSize(); @@ -1368,8 +1376,8 @@ private void applyRemoteSettings(final H2Config config) throws H2ConnectionExcep } } private void applyLocalSettings() throws H2ConnectionException { - hPackDecoder.setMaxTableSize(localConfig.getHeaderTableSize()); - hPackDecoder.setMaxListSize(localConfig.getMaxHeaderListSize()); + hPackDecoder.setMaxTableSize((int) localConfig.getHeaderTableSize()); + hPackDecoder.setMaxListSize((int) localConfig.getMaxHeaderListSize()); final int delta = localConfig.getInitialWindowSize() - initInputWinSize; initInputWinSize = localConfig.getInitialWindowSize(); @@ -1699,7 +1707,7 @@ public void markLocalClosed() { } @Override - public boolean localReset(final int code) throws IOException { + public boolean localReset(final long code) throws IOException { ioSession.getLock().lock(); try { if (isLocalReset()) { diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientH2StreamMultiplexer.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientH2StreamMultiplexer.java index 83b7b4d629..24af533f62 100644 --- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientH2StreamMultiplexer.java +++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ClientH2StreamMultiplexer.java @@ -121,7 +121,7 @@ public ClientH2StreamMultiplexer( } @Override - void validateSetting(final H2Param param, final int value) throws H2ConnectionException { + void validateSetting(final H2Param param, final long value) throws H2ConnectionException { if (param == H2Param.ENABLE_PUSH) { if (value != 0 && value != 1) { throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "Illegal ENABLE_PUSH setting: " + value); diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/H2Stream.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/H2Stream.java index ad080cecc9..f4ed0bb8da 100644 --- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/H2Stream.java +++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/H2Stream.java @@ -241,7 +241,7 @@ void fail(final Exception cause) { } } - void localReset(final Exception cause, final int code) throws IOException { + void localReset(final Exception cause, final long code) throws IOException { channel.localReset(code); if (released.compareAndSet(false, true)) { try { diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/H2StreamChannel.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/H2StreamChannel.java index d86159ef9a..23c935ff35 100644 --- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/H2StreamChannel.java +++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/H2StreamChannel.java @@ -55,7 +55,7 @@ interface H2StreamChannel extends DataStreamChannel, CapacityChannel, Cancellabl void markLocalClosed(); - boolean localReset(int errorCode) throws IOException; + boolean localReset(long errorCode) throws IOException; default boolean localReset(H2Error error) throws IOException { return localReset(error != null ? error.getCode() : H2Error.INTERNAL_ERROR.getCode()); diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/H2Streams.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/H2Streams.java index 09847a7d01..c87972ee8b 100644 --- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/H2Streams.java +++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/H2Streams.java @@ -126,7 +126,7 @@ public H2Stream createReserved(final H2StreamChannel channel, final H2StreamHand return createStream(channel, streamHandler); } - public void resetIfExceedsMaxConcurrentLimit(final H2Stream stream, final int max) throws IOException { + public void resetIfExceedsMaxConcurrentLimit(final H2Stream stream, final long max) throws IOException { if (stream.isActive() && getRemoteCount() > max) { stream.localReset(new H2StreamResetException(H2Error.REFUSED_STREAM, "Local SETTINGS_MAX_CONCURRENT_STREAMS exceeded")); } diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerH2StreamMultiplexer.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerH2StreamMultiplexer.java index 9a70b3c959..14c4ff5e70 100644 --- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerH2StreamMultiplexer.java +++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/ServerH2StreamMultiplexer.java @@ -89,7 +89,7 @@ public ServerH2StreamMultiplexer( } @Override - void validateSetting(final H2Param param, final int value) throws H2ConnectionException { + void validateSetting(final H2Param param, final long value) throws H2ConnectionException { if (param == H2Param.ENABLE_PUSH) { if (value != 0 && value != 1) { throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "Illegal ENABLE_PUSH setting: " + value); diff --git a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/TestH2Error.java b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/TestH2Error.java new file mode 100644 index 0000000000..7d817246f9 --- /dev/null +++ b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/TestH2Error.java @@ -0,0 +1,62 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.hc.core5.http2; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class TestH2Error { + + @Test + void testGetByCode() { + Assertions.assertEquals(H2Error.PROTOCOL_ERROR, H2Error.getByCode(0x1L)); + Assertions.assertEquals(H2Error.HTTP_1_1_REQUIRED, H2Error.getByCode(0xdL)); + Assertions.assertNull(H2Error.getByCode(0x7fffffffL)); + Assertions.assertNull(H2Error.getByCode(0x80000000L)); + Assertions.assertNull(H2Error.getByCode(0xffffffffL)); + Assertions.assertEquals(H2Error.PROTOCOL_ERROR, H2Error.getByCode(0x1)); + Assertions.assertNull(H2Error.getByCode(0xffffffff)); + } + + @Test + void testExceptionErrorCodeRange() { + Assertions.assertEquals(0xffffffffL, new H2ConnectionException(0xffffffffL, "test").getCode()); + Assertions.assertEquals(0x80000000L, new H2StreamResetException(0x80000000L, "test").getCode()); + Assertions.assertThrows(IllegalArgumentException.class, () -> new H2ConnectionException(-1L, "test")); + Assertions.assertThrows(IllegalArgumentException.class, () -> new H2ConnectionException(0x100000000L, "test")); + Assertions.assertThrows(IllegalArgumentException.class, () -> new H2StreamResetException(-1L, "test")); + Assertions.assertThrows(IllegalArgumentException.class, () -> new H2StreamResetException(0x100000000L, "test")); + } + + @Test + void testExceptionIntCodeIsRawUnsigned() { + Assertions.assertEquals(0xffffffffL, new H2ConnectionException(0xffffffff, "test").getCode()); + Assertions.assertEquals(0x80000000L, new H2StreamResetException(0x80000000, "test").getCode()); + Assertions.assertEquals(H2Error.CANCEL.getCode(), new H2StreamResetException(0x8, "test").getCode()); + } + +} diff --git a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/config/H2ConfigTest.java b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/config/H2ConfigTest.java index a6a2859d5f..106215dd9c 100644 --- a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/config/H2ConfigTest.java +++ b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/config/H2ConfigTest.java @@ -30,12 +30,54 @@ import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; class H2ConfigTest { + @Test + void unsignedSettingsRange() { + for (final long value : new long[] {0L, 0x7fffffffL, 0x80000000L, 0xffffffffL}) { + final H2Config h2Config = H2Config.custom() + .setHeaderTableSize(value) + .setMaxConcurrentStreams(value) + .setMaxHeaderListSize(value) + .build(); + assertEquals(value, h2Config.getHeaderTableSize()); + assertEquals(value, h2Config.getMaxConcurrentStreams()); + assertEquals(value, h2Config.getMaxHeaderListSize()); + } + for (final long value : new long[] {-1L, 0x100000000L}) { + assertThrows(IllegalArgumentException.class, () -> H2Config.custom().setHeaderTableSize(value)); + assertThrows(IllegalArgumentException.class, () -> H2Config.custom().setMaxConcurrentStreams(value)); + assertThrows(IllegalArgumentException.class, () -> H2Config.custom().setMaxHeaderListSize(value)); + } + } + + @Test + void semanticIntSettingsRejectNegative() { + final H2Config h2Config = H2Config.custom() + .setHeaderTableSize(Integer.MAX_VALUE) + .setMaxConcurrentStreams(0) + .setMaxHeaderListSize(Integer.MAX_VALUE) + .build(); + assertEquals(2147483647L, h2Config.getHeaderTableSize()); + assertEquals(0L, h2Config.getMaxConcurrentStreams()); + assertEquals(2147483647L, h2Config.getMaxHeaderListSize()); + assertThrows(IllegalArgumentException.class, () -> H2Config.custom().setHeaderTableSize(-1)); + assertThrows(IllegalArgumentException.class, () -> H2Config.custom().setMaxConcurrentStreams(-1)); + assertThrows(IllegalArgumentException.class, () -> H2Config.custom().setMaxHeaderListSize(-1)); + } + + @Test + void constrainedSettingsRange() { + assertThrows(IllegalArgumentException.class, () -> H2Config.custom().setInitialWindowSize(-1)); + assertThrows(IllegalArgumentException.class, () -> H2Config.custom().setMaxFrameSize(16383)); + assertThrows(IllegalArgumentException.class, () -> H2Config.custom().setMaxFrameSize(16777216)); + } + @Test void builder() { // Create and start requester diff --git a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/frame/TestDefaultFrameFactory.java b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/frame/TestDefaultFrameFactory.java index f5cbe857b8..f9e3e1856f 100644 --- a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/frame/TestDefaultFrameFactory.java +++ b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/frame/TestDefaultFrameFactory.java @@ -81,6 +81,24 @@ void testResetStreamFrame() { Assertions.assertEquals(H2Error.INTERNAL_ERROR.getCode(), payload.getInt()); } + @Test + void testUnsignedValuesWrittenToWire() { + final FrameFactory frameFactory = new DefaultFrameFactory(); + final ByteBuffer settings = frameFactory.createSettings( + new H2Setting(H2Param.MAX_HEADER_LIST_SIZE, 0xffffffffL)).getPayload(); + Assertions.assertEquals(H2Param.MAX_HEADER_LIST_SIZE.getCode(), settings.getShort()); + Assertions.assertEquals(0xffffffffL, Integer.toUnsignedLong(settings.getInt())); + + Assertions.assertEquals(0x80000000L, Integer.toUnsignedLong( + frameFactory.createResetStream(1, 0x80000000L).getPayload().getInt())); + Assertions.assertEquals(0xffffffffL, Integer.toUnsignedLong( + frameFactory.createResetStream(1, 0xffffffffL).getPayload().getInt())); + Assertions.assertThrows(IllegalArgumentException.class, () -> frameFactory.createResetStream(1, -1L)); + Assertions.assertThrows(IllegalArgumentException.class, () -> frameFactory.createResetStream(1, 0x100000000L)); + Assertions.assertEquals(0xffffffffL, Integer.toUnsignedLong( + frameFactory.createResetStream(1, 0xffffffff).getPayload().getInt())); + } + @Test void testGoAwayFrame() { diff --git a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/frame/TestH2Settings.java b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/frame/TestH2Settings.java index c8486708dd..62ebbff352 100644 --- a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/frame/TestH2Settings.java +++ b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/frame/TestH2Settings.java @@ -55,4 +55,18 @@ void testH2SettingBasics() { Assertions.assertEquals("INITIAL_WINDOW_SIZE: 1024", setting2.toString()); } + @Test + void testH2SettingUnsignedValueRange() { + Assertions.assertEquals(0L, new H2Setting(H2Param.HEADER_TABLE_SIZE, 0L).getValue()); + Assertions.assertEquals(2147483647L, new H2Setting(H2Param.HEADER_TABLE_SIZE, 0x7fffffffL).getValue()); + Assertions.assertEquals(2147483648L, new H2Setting(H2Param.HEADER_TABLE_SIZE, 0x80000000L).getValue()); + Assertions.assertEquals(4294967295L, new H2Setting(H2Param.HEADER_TABLE_SIZE, 0xffffffffL).getValue()); + Assertions.assertEquals("MAX_HEADER_LIST_SIZE: 4294967295", + new H2Setting(H2Param.MAX_HEADER_LIST_SIZE, 0xffffffffL).toString()); + Assertions.assertThrows(IllegalArgumentException.class, () -> new H2Setting(H2Param.HEADER_TABLE_SIZE, -1L)); + Assertions.assertThrows(IllegalArgumentException.class, () -> new H2Setting(H2Param.HEADER_TABLE_SIZE, 0x100000000L)); + Assertions.assertEquals(0x7fffffffL, new H2Setting(H2Param.HEADER_TABLE_SIZE, 0x7fffffff).getValue()); + Assertions.assertThrows(IllegalArgumentException.class, () -> new H2Setting(H2Param.HEADER_TABLE_SIZE, -1)); + } + } diff --git a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/nio/TestAbstractH2StreamMultiplexer.java b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/nio/TestAbstractH2StreamMultiplexer.java index d3eaf8d707..ba85f34fd2 100644 --- a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/nio/TestAbstractH2StreamMultiplexer.java +++ b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/nio/TestAbstractH2StreamMultiplexer.java @@ -156,7 +156,7 @@ public H2StreamMultiplexerImpl( } @Override - void validateSetting(final H2Param param, final int value) throws H2ConnectionException { + void validateSetting(final H2Param param, final long value) throws H2ConnectionException { } @Override @@ -323,7 +323,7 @@ void testInputHeaderContinuationFrame() throws Exception { .build(); final ByteArrayBuffer buf = new ByteArrayBuffer(19); - final HPackEncoder encoder = new HPackEncoder(H2Config.INIT.getHeaderTableSize(), CharCodingSupport.createEncoder(CharCodingConfig.DEFAULT)); + final HPackEncoder encoder = new HPackEncoder((int) H2Config.INIT.getHeaderTableSize(), CharCodingSupport.createEncoder(CharCodingConfig.DEFAULT)); final List
headers = new ArrayList<>(); headers.add(new BasicHeader("test-header-key", "value")); headers.add(new BasicHeader(":status", "200")); @@ -369,7 +369,7 @@ void testZeroIncrement() throws Exception { () -> streamHandler); final ByteArrayBuffer headerBuf = new ByteArrayBuffer(200); - final HPackEncoder encoder = new HPackEncoder(h2Config.getHeaderTableSize(), + final HPackEncoder encoder = new HPackEncoder((int) h2Config.getHeaderTableSize(), CharCodingSupport.createEncoder(CharCodingConfig.DEFAULT)); final List
headers = Arrays.asList( @@ -468,7 +468,7 @@ void testIncrementOverflow() throws Exception { () -> streamHandler); final ByteArrayBuffer headerBuf = new ByteArrayBuffer(200); - final HPackEncoder encoder = new HPackEncoder(h2Config.getHeaderTableSize(), + final HPackEncoder encoder = new HPackEncoder((int) h2Config.getHeaderTableSize(), CharCodingSupport.createEncoder(CharCodingConfig.DEFAULT)); final List
headers = Arrays.asList( @@ -523,7 +523,7 @@ void testHeadersAfterEndOfStream() throws Exception { () -> streamHandler); final ByteArrayBuffer headerBuf = new ByteArrayBuffer(200); - final HPackEncoder encoder = new HPackEncoder(h2Config.getHeaderTableSize(), + final HPackEncoder encoder = new HPackEncoder((int) h2Config.getHeaderTableSize(), CharCodingSupport.createEncoder(CharCodingConfig.DEFAULT)); final List
headers = Arrays.asList( @@ -579,7 +579,7 @@ void testDataAfterEndOfStream() throws Exception { () -> streamHandler); final ByteArrayBuffer headerBuf = new ByteArrayBuffer(200); - final HPackEncoder encoder = new HPackEncoder(h2Config.getHeaderTableSize(), + final HPackEncoder encoder = new HPackEncoder((int) h2Config.getHeaderTableSize(), CharCodingSupport.createEncoder(CharCodingConfig.DEFAULT)); final List
headers = Arrays.asList( @@ -635,7 +635,7 @@ void testContinuationAfterEndOfStream() throws Exception { () -> streamHandler); final ByteArrayBuffer headerBuf = new ByteArrayBuffer(200); - final HPackEncoder encoder = new HPackEncoder(h2Config.getHeaderTableSize(), + final HPackEncoder encoder = new HPackEncoder((int) h2Config.getHeaderTableSize(), CharCodingSupport.createEncoder(CharCodingConfig.DEFAULT)); final List
headers = Arrays.asList( @@ -673,7 +673,7 @@ void testInputHeaderContinuationFramesNoLimit() throws Exception { .build(); final ByteArrayBuffer headerBuf = new ByteArrayBuffer(19); - final HPackEncoder encoder = new HPackEncoder(H2Config.INIT.getHeaderTableSize(), CharCodingSupport.createEncoder(CharCodingConfig.DEFAULT)); + final HPackEncoder encoder = new HPackEncoder((int) H2Config.INIT.getHeaderTableSize(), CharCodingSupport.createEncoder(CharCodingConfig.DEFAULT)); final List
headers = new ArrayList<>(); headers.add(new BasicHeader(":status", "200")); for (int i = 1; i <= 100; i++) { @@ -727,7 +727,7 @@ void testInputHeaderContinuationFramesMaxLimit() throws Exception { .build(); final ByteArrayBuffer headerBuf = new ByteArrayBuffer(19); - final HPackEncoder encoder = new HPackEncoder(H2Config.INIT.getHeaderTableSize(), CharCodingSupport.createEncoder(CharCodingConfig.DEFAULT)); + final HPackEncoder encoder = new HPackEncoder((int) H2Config.INIT.getHeaderTableSize(), CharCodingSupport.createEncoder(CharCodingConfig.DEFAULT)); final List
headers = new ArrayList<>(); headers.add(new BasicHeader(":status", "200")); for (int i = 1; i <= 100; i++) { @@ -792,7 +792,7 @@ void testStreamRemoteReset() throws Exception { final H2Stream stream = streamMultiplexer.createStream(channel, streamHandler); final ByteArrayBuffer buf = new ByteArrayBuffer(19); - final HPackEncoder encoder = new HPackEncoder(H2Config.INIT.getHeaderTableSize(), CharCodingSupport.createEncoder(CharCodingConfig.DEFAULT)); + final HPackEncoder encoder = new HPackEncoder((int) H2Config.INIT.getHeaderTableSize(), CharCodingSupport.createEncoder(CharCodingConfig.DEFAULT)); final List
headers = new ArrayList<>(); headers.add(new BasicHeader(":status", "200")); encoder.encodeHeaders(buf, headers, h2Config.isCompressionEnabled()); @@ -838,7 +838,7 @@ void testStreamRemoteResetNoErrorRemoteAlreadyClosed() throws Exception { final H2Stream stream = streamMultiplexer.createStream(channel, streamHandler); final ByteArrayBuffer buf = new ByteArrayBuffer(19); - final HPackEncoder encoder = new HPackEncoder(H2Config.INIT.getHeaderTableSize(), CharCodingSupport.createEncoder(CharCodingConfig.DEFAULT)); + final HPackEncoder encoder = new HPackEncoder((int) H2Config.INIT.getHeaderTableSize(), CharCodingSupport.createEncoder(CharCodingConfig.DEFAULT)); final List
headers = new ArrayList<>(); headers.add(new BasicHeader(":status", "200")); encoder.encodeHeaders(buf, headers, h2Config.isCompressionEnabled()); @@ -1269,7 +1269,7 @@ void testExpiredStreamResetOnInboundData() throws Exception { // Encode request headers final ByteArrayBuffer headerBuf = new ByteArrayBuffer(200); - final HPackEncoder encoder = new HPackEncoder(h2Config.getHeaderTableSize(), + final HPackEncoder encoder = new HPackEncoder((int) h2Config.getHeaderTableSize(), CharCodingSupport.createEncoder(CharCodingConfig.DEFAULT)); final List
headers = Arrays.asList( new BasicHeader(":method", "GET"), @@ -1341,7 +1341,7 @@ void testExpiredStreamResetOnInboundContinuation() throws Exception { // Encode request headers final ByteArrayBuffer headerBuf = new ByteArrayBuffer(200); - final HPackEncoder encoder = new HPackEncoder(h2Config.getHeaderTableSize(), + final HPackEncoder encoder = new HPackEncoder((int) h2Config.getHeaderTableSize(), CharCodingSupport.createEncoder(CharCodingConfig.DEFAULT)); final List
headers = Arrays.asList( new BasicHeader(":method", "GET"), @@ -1984,7 +1984,7 @@ void testPushPromiseReservedBitInPromisedStreamIdIgnored() throws Exception { final ByteArrayBuffer hbuf = new ByteArrayBuffer(256); final HPackEncoder encoder = new HPackEncoder( - H2Config.INIT.getHeaderTableSize(), + (int) H2Config.INIT.getHeaderTableSize(), CharCodingSupport.createEncoder(CharCodingConfig.DEFAULT)); final List
reqHeaders = Arrays.asList( @@ -2037,7 +2037,7 @@ void testGoAwayReservedBitInLastStreamIdIgnored() throws Exception { final ByteBuffer goAwayPayload = ByteBuffer.allocate(8); goAwayPayload.putInt(0x80000003); // reserved bit set, last-stream-id = 3 - goAwayPayload.putInt(H2Error.NO_ERROR.getCode()); + goAwayPayload.putInt((int) H2Error.NO_ERROR.getCode()); goAwayPayload.flip(); final RawFrame goAway = new RawFrame(FrameType.GOAWAY.getValue(), 0, 0, goAwayPayload); @@ -2223,7 +2223,7 @@ void testHeadersWithPrioritySelfDependencyIsStreamProtocolError() throws Excepti final ByteArrayBuffer headerBuf = new ByteArrayBuffer(128); final HPackEncoder encoder = new HPackEncoder( - h2Config.getHeaderTableSize(), + (int) h2Config.getHeaderTableSize(), CharCodingSupport.createEncoder(CharCodingConfig.DEFAULT)); final List
headers = Arrays.asList( @@ -2309,7 +2309,7 @@ private int encoderTableSizeAfterRemoteSettings(final int headerTableSize) throw @Test void testRemoteHeaderTableSizeIsCappedForEncoder() throws Exception { Assertions.assertEquals( - H2Config.INIT.getHeaderTableSize(), + (int) H2Config.INIT.getHeaderTableSize(), encoderTableSizeAfterRemoteSettings(Integer.MAX_VALUE)); } @@ -2389,9 +2389,9 @@ void testUnsignedHeaderTableSizeSettingAccepted() throws Exception { Assertions.assertDoesNotThrow( () -> mux.onInput(ByteBuffer.wrap(encodeFrame(settingsFrame)))); - Assertions.assertEquals(-1, getRemoteConfig(mux).getHeaderTableSize()); + Assertions.assertEquals(0xffffffffL, getRemoteConfig(mux).getHeaderTableSize()); Assertions.assertEquals( - H2Config.INIT.getHeaderTableSize(), + (int) H2Config.INIT.getHeaderTableSize(), getHPackEncoder(mux).getMaxTableSize()); } finally { mux.close(); @@ -2421,7 +2421,7 @@ void testUnsignedMaxConcurrentStreamsSettingAccepted() throws Exception { Assertions.assertDoesNotThrow( () -> mux.onInput(ByteBuffer.wrap(encodeFrame(settingsFrame)))); Assertions.assertEquals( - Integer.MIN_VALUE, + 0x80000000L, getRemoteConfig(mux).getMaxConcurrentStreams()); } finally { mux.close(); @@ -2450,7 +2450,163 @@ void testUnsignedMaxHeaderListSizeSettingAccepted() throws Exception { Assertions.assertDoesNotThrow( () -> mux.onInput(ByteBuffer.wrap(encodeFrame(settingsFrame)))); - Assertions.assertEquals(-1, getRemoteConfig(mux).getMaxHeaderListSize()); + Assertions.assertEquals(0xffffffffL, getRemoteConfig(mux).getMaxHeaderListSize()); + } finally { + mux.close(); + } + } + + private AbstractH2StreamMultiplexer createUnsignedValueTestMultiplexer() { + return new H2StreamMultiplexerImpl( + protocolIOSession, + FRAME_FACTORY, + StreamIdGenerator.ODD, + httpProcessor, + CharCodingConfig.DEFAULT, + H2Config.custom().build(), + h2StreamListener, + () -> streamHandler); + } + + private static RawFrame createSettingsFrame(final H2Param param, final int rawValue) { + final ByteBuffer payload = ByteBuffer.allocate(6); + payload.putShort((short) param.getCode()); + payload.putInt(rawValue); + payload.flip(); + return new RawFrame(FrameType.SETTINGS.getValue(), 0, 0, payload); + } + + @Test + void testLocalHpackLimitsAboveDecoderCapacityRejected() { + Assertions.assertThrows(IllegalArgumentException.class, () -> new H2StreamMultiplexerImpl( + protocolIOSession, FRAME_FACTORY, StreamIdGenerator.ODD, httpProcessor, CharCodingConfig.DEFAULT, + H2Config.custom().setHeaderTableSize(0x80000000L).build(), h2StreamListener, () -> streamHandler)); + Assertions.assertThrows(IllegalArgumentException.class, () -> new H2StreamMultiplexerImpl( + protocolIOSession, FRAME_FACTORY, StreamIdGenerator.ODD, httpProcessor, CharCodingConfig.DEFAULT, + H2Config.custom().setMaxHeaderListSize(0xffffffffL).build(), h2StreamListener, () -> streamHandler)); + } + + @Test + void testUnsignedSettingsValuesObservedAsPositiveLong() throws Exception { + final int[] rawValues = {0x00000000, 0x7fffffff, 0x80000000, 0xffffffff}; + final long[] expected = {0L, 2147483647L, 2147483648L, 4294967295L}; + for (final H2Param param : new H2Param[] { + H2Param.HEADER_TABLE_SIZE, H2Param.MAX_CONCURRENT_STREAMS, H2Param.MAX_HEADER_LIST_SIZE}) { + for (int i = 0; i < rawValues.length; i++) { + final AbstractH2StreamMultiplexer mux = createUnsignedValueTestMultiplexer(); + try { + mux.onInput(ByteBuffer.wrap(encodeFrame(createSettingsFrame(param, rawValues[i])))); + final H2Config remoteConfig = getRemoteConfig(mux); + final long actual = param == H2Param.HEADER_TABLE_SIZE ? remoteConfig.getHeaderTableSize() + : param == H2Param.MAX_CONCURRENT_STREAMS ? remoteConfig.getMaxConcurrentStreams() + : remoteConfig.getMaxHeaderListSize(); + Assertions.assertEquals(expected[i], actual, param + " " + Integer.toHexString(rawValues[i])); + } finally { + mux.close(); + } + } + } + } + + @Test + void testZeroMaxConcurrentStreamsSettingAccepted() throws Exception { + final AbstractH2StreamMultiplexer mux = createUnsignedValueTestMultiplexer(); + try { + mux.onInput(ByteBuffer.wrap(encodeFrame(createSettingsFrame(H2Param.MAX_CONCURRENT_STREAMS, 0)))); + Assertions.assertEquals(0L, getRemoteConfig(mux).getMaxConcurrentStreams()); + } finally { + mux.close(); + } + } + + @Test + void testInitialWindowSizeSettingAboveMaxIsFlowControlError() throws Exception { + for (final int rawValue : new int[] {0x80000000, 0xffffffff}) { + final AbstractH2StreamMultiplexer mux = createUnsignedValueTestMultiplexer(); + try { + final H2ConnectionException ex = Assertions.assertThrows(H2ConnectionException.class, + () -> mux.onInput(ByteBuffer.wrap(encodeFrame(createSettingsFrame(H2Param.INITIAL_WINDOW_SIZE, rawValue))))); + Assertions.assertEquals(H2Error.FLOW_CONTROL_ERROR, H2Error.getByCode(ex.getCode())); + } finally { + mux.close(); + } + } + final AbstractH2StreamMultiplexer mux = createUnsignedValueTestMultiplexer(); + try { + mux.onInput(ByteBuffer.wrap(encodeFrame(createSettingsFrame(H2Param.INITIAL_WINDOW_SIZE, 0x7fffffff)))); + Assertions.assertEquals(0x7fffffff, getRemoteConfig(mux).getInitialWindowSize()); + } finally { + mux.close(); + } + } + + @Test + void testInvalidMaxFrameSizeSettingIsProtocolError() throws Exception { + for (final int rawValue : new int[] {0, 0x3fff, 0x01000000, 0x7fffffff, 0x80000000, 0xffffffff}) { + final AbstractH2StreamMultiplexer mux = createUnsignedValueTestMultiplexer(); + try { + final H2ConnectionException ex = Assertions.assertThrows(H2ConnectionException.class, + () -> mux.onInput(ByteBuffer.wrap(encodeFrame(createSettingsFrame(H2Param.MAX_FRAME_SIZE, rawValue)))), + Integer.toHexString(rawValue)); + Assertions.assertEquals(H2Error.PROTOCOL_ERROR, H2Error.getByCode(ex.getCode())); + } finally { + mux.close(); + } + } + for (final int rawValue : new int[] {0x4000, 0xffffff}) { + final AbstractH2StreamMultiplexer mux = createUnsignedValueTestMultiplexer(); + try { + mux.onInput(ByteBuffer.wrap(encodeFrame(createSettingsFrame(H2Param.MAX_FRAME_SIZE, rawValue)))); + Assertions.assertEquals(rawValue, getRemoteConfig(mux).getMaxFrameSize()); + } finally { + mux.close(); + } + } + } + + @Test + void testRstStreamErrorCodeObservedAsUnsignedLong() throws Exception { + final int[] rawCodes = {0x7fffffff, 0x80000000, 0xffffffff, (int) H2Error.PROTOCOL_ERROR.getCode()}; + final long[] expected = {2147483647L, 2147483648L, 4294967295L, 1L}; + for (int i = 0; i < rawCodes.length; i++) { + final AbstractH2StreamMultiplexer mux = createUnsignedValueTestMultiplexer(); + final H2StreamHandler handler = Mockito.mock(H2StreamHandler.class); + try { + mux.createStream(mux.createChannel(1), handler); + final ByteBuffer payload = ByteBuffer.allocate(4); + payload.putInt(rawCodes[i]); + payload.flip(); + mux.onInput(ByteBuffer.wrap(encodeFrame(new RawFrame(FrameType.RST_STREAM.getValue(), 0, 1, payload)))); + + final ArgumentCaptor captor = ArgumentCaptor.forClass(Exception.class); + Mockito.verify(handler).failed(captor.capture()); + final H2StreamResetException ex = Assertions.assertInstanceOf(H2StreamResetException.class, captor.getValue()); + Assertions.assertEquals(expected[i], ex.getCode()); + } finally { + mux.close(); + } + } + Assertions.assertEquals(H2Error.PROTOCOL_ERROR, H2Error.getByCode(1L)); + Assertions.assertNull(H2Error.getByCode(2147483648L)); + Assertions.assertNull(H2Error.getByCode(4294967295L)); + } + + @Test + void testGoAwayErrorCodeObservedAsUnsignedLong() throws Exception { + final AbstractH2StreamMultiplexer mux = createUnsignedValueTestMultiplexer(); + final H2StreamHandler handler = Mockito.mock(H2StreamHandler.class); + try { + mux.createStream(mux.createChannel(1), handler); + final ByteBuffer payload = ByteBuffer.allocate(8); + payload.putInt(1); + payload.putInt(0xffffffff); + payload.flip(); + mux.onInput(ByteBuffer.wrap(encodeFrame(new RawFrame(FrameType.GOAWAY.getValue(), 0, 0, payload)))); + + final ArgumentCaptor captor = ArgumentCaptor.forClass(Exception.class); + Mockito.verify(handler).failed(captor.capture()); + final H2StreamResetException ex = Assertions.assertInstanceOf(H2StreamResetException.class, captor.getValue()); + Assertions.assertEquals(4294967295L, ex.getCode()); } finally { mux.close(); } @@ -2503,7 +2659,7 @@ void testDataOnReservedRemoteStreamIsConnectionProtocolError() throws Exception // Receive PUSH_PROMISE for stream 2 -> stream 2 becomes reserved (remote). final ByteArrayBuffer headerBuf = new ByteArrayBuffer(256); final HPackEncoder encoder = new HPackEncoder( - H2Config.INIT.getHeaderTableSize(), + (int) H2Config.INIT.getHeaderTableSize(), CharCodingSupport.createEncoder(CharCodingConfig.DEFAULT)); final List
headers = Arrays.asList( diff --git a/pom.xml b/pom.xml index 6340fc527e..d36a9be5b7 100644 --- a/pom.xml +++ b/pom.xml @@ -274,6 +274,14 @@ @org.apache.hc.core5.annotation.Internal org.apache.hc.core5.testing.reactive.ReactiveTestUtils org.apache.hc.core5.testing.framework.* + + org.apache.hc.core5.http2.H2ConnectionException#getCode() + org.apache.hc.core5.http2.H2Error#getCode() + org.apache.hc.core5.http2.H2StreamResetException#getCode() + org.apache.hc.core5.http2.config.H2Config#getHeaderTableSize() + org.apache.hc.core5.http2.config.H2Config#getMaxConcurrentStreams() + org.apache.hc.core5.http2.config.H2Config#getMaxHeaderListSize() + org.apache.hc.core5.http2.config.H2Setting#getValue() true false @@ -422,6 +430,14 @@ @org.apache.hc.core5.annotation.Internal org.apache.hc.core5.testing.reactive.ReactiveTestUtils org.apache.hc.core5.testing.framework.* + + org.apache.hc.core5.http2.H2ConnectionException#getCode() + org.apache.hc.core5.http2.H2Error#getCode() + org.apache.hc.core5.http2.H2StreamResetException#getCode() + org.apache.hc.core5.http2.config.H2Config#getHeaderTableSize() + org.apache.hc.core5.http2.config.H2Config#getMaxConcurrentStreams() + org.apache.hc.core5.http2.config.H2Config#getMaxHeaderListSize() + org.apache.hc.core5.http2.config.H2Setting#getValue() true