Skip to content

Commit

Permalink
Refactor setting handling so common validation is applied to local an…
Browse files Browse the repository at this point in the history
…d remote

git-svn-id: https://svn.apache.org/repos/asf/tomcat/trunk@1696754 13f79535-47bb-0310-9956-ffa450edef68
  • Loading branch information
markt-asf committed Aug 20, 2015
1 parent 27f002a commit b7110d3
Show file tree
Hide file tree
Showing 14 changed files with 305 additions and 288 deletions.
4 changes: 2 additions & 2 deletions java/org/apache/coyote/http2/AbstractStream.java
Expand Up @@ -35,7 +35,7 @@ abstract class AbstractStream {


private volatile AbstractStream parentStream = null; private volatile AbstractStream parentStream = null;
private final Set<AbstractStream> childStreams = new HashSet<>(); private final Set<AbstractStream> childStreams = new HashSet<>();
private long windowSize = ConnectionSettingsRemote.DEFAULT_INITIAL_WINDOW_SIZE; private long windowSize = ConnectionSettingsBase.DEFAULT_INITIAL_WINDOW_SIZE;


public Integer getIdentifier() { public Integer getIdentifier() {
return identifier; return identifier;
Expand Down Expand Up @@ -119,7 +119,7 @@ protected synchronized void incrementWindowSize(int increment) throws Http2Excep
getIdentifier(), Integer.toString(increment), Long.toString(windowSize))); getIdentifier(), Integer.toString(increment), Long.toString(windowSize)));
} }


if (windowSize > ConnectionSettingsRemote.MAX_WINDOW_SIZE) { if (windowSize > ConnectionSettingsBase.MAX_WINDOW_SIZE) {
String msg = sm.getString("abstractStream.windowSizeTooBig", getConnectionId(), identifier, String msg = sm.getString("abstractStream.windowSizeTooBig", getConnectionId(), identifier,
Integer.toString(increment), Long.toString(windowSize)); Integer.toString(increment), Long.toString(windowSize));
if (identifier.intValue() == 0) { if (identifier.intValue() == 0) {
Expand Down
194 changes: 193 additions & 1 deletion java/org/apache/coyote/http2/ConnectionSettingsBase.java
Expand Up @@ -16,6 +16,198 @@
*/ */
package org.apache.coyote.http2; package org.apache.coyote.http2;


public abstract class ConnectionSettingsBase { import java.util.HashMap;
import java.util.Map;


import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.res.StringManager;

public abstract class ConnectionSettingsBase<T extends Throwable> {

private final Log log = LogFactory.getLog(ConnectionSettingsBase.class);
private final StringManager sm = StringManager.getManager(ConnectionSettingsBase.class);

// Limits
protected static final int MAX_WINDOW_SIZE = (1 << 31) - 1;
protected static final int MIN_MAX_FRAME_SIZE = 1 << 14;
protected static final int MAX_MAX_FRAME_SIZE = (1 << 24) - 1;
protected static final long UNLIMITED = ((long)1 << 32); // Use the maximum possible

// Defaults
protected static final int DEFAULT_HEADER_TABLE_SIZE = 4096;
protected static final boolean DEFAULT_ENABLE_PUSH = true;
protected static final long DEFAULT_MAX_CONCURRENT_STREAMS = UNLIMITED;
protected static final int DEFAULT_INITIAL_WINDOW_SIZE = (1 << 16) - 1;
protected static final int DEFAULT_MAX_FRAME_SIZE = MIN_MAX_FRAME_SIZE;
protected static final long DEFAULT_MAX_HEADER_LIST_SIZE = UNLIMITED;

protected Map<Setting,Long> current = new HashMap<>();
protected Map<Setting,Long> pending = new HashMap<>();


public ConnectionSettingsBase() {
// Set up the defaults
current.put(Setting.HEADER_TABLE_SIZE, Long.valueOf(DEFAULT_HEADER_TABLE_SIZE));
current.put(Setting.ENABLE_PUSH, Long.valueOf(DEFAULT_ENABLE_PUSH ? 1 : 0));
current.put(Setting.MAX_CONCURRENT_STREAMS, Long.valueOf(DEFAULT_MAX_CONCURRENT_STREAMS));
current.put(Setting.INITIAL_WINDOW_SIZE, Long.valueOf(DEFAULT_INITIAL_WINDOW_SIZE));
current.put(Setting.MAX_FRAME_SIZE, Long.valueOf(DEFAULT_MAX_FRAME_SIZE));
current.put(Setting.MAX_HEADER_LIST_SIZE, Long.valueOf(DEFAULT_MAX_HEADER_LIST_SIZE));
}


public void set(Setting setting, long value) throws T {
if (log.isDebugEnabled()) {
log.debug(sm.getString("connectionSettings.debug", setting, Long.toString(value)));
}

switch(setting) {
case HEADER_TABLE_SIZE:
validateHeaderTableSize(value);
break;
case ENABLE_PUSH:
validateEnablePush(value);
break;
case MAX_CONCURRENT_STREAMS:
// No further validation required
break;
case INITIAL_WINDOW_SIZE:
validateInitialWindowSize(value);
break;
case MAX_FRAME_SIZE:
validateMaxFrameSize(value);
break;
case MAX_HEADER_LIST_SIZE:
// No further validation required
break;
case UNKNOWN:
// Unrecognised. Ignore it.
log.warn(sm.getString("connectionSettings.unknown", setting, Long.toString(value)));
return;
}

set(setting, Long.valueOf(value));
}


synchronized void set(Setting setting, Long value) {
current.put(setting, value);
}


public int getHeaderTableSize() {
return getMinInt(Setting.HEADER_TABLE_SIZE);
}


public boolean getEnablePush() {
long result = getMin(Setting.ENABLE_PUSH);
return result != 0;
}


public long getMaxConcurrentStreams() {
return getMax(Setting.MAX_CONCURRENT_STREAMS);
}


public int getInitialWindowSize() {
return getMaxInt(Setting.INITIAL_WINDOW_SIZE);
}


public int getMaxFrameSize() {
return getMaxInt(Setting.MAX_FRAME_SIZE);
}


public long getMaxHeaderListSize() {
return getMax(Setting.MAX_HEADER_LIST_SIZE);
}


private synchronized long getMin(Setting setting) {
Long pendingValue = pending.get(setting);
long currentValue = current.get(setting).longValue();
if (pendingValue == null) {
return currentValue;
} else {
return Long.min(pendingValue.longValue(), currentValue);
}
}


private synchronized int getMinInt(Setting setting) {
long result = getMin(setting);
if (result > Integer.MAX_VALUE) {
return Integer.MAX_VALUE;
} else {
return (int) result;
}
}


private synchronized long getMax(Setting setting) {
Long pendingValue = pending.get(setting);
long currentValue = current.get(setting).longValue();
if (pendingValue == null) {
return currentValue;
} else {
return Long.max(pendingValue.longValue(), currentValue);
}
}


private synchronized int getMaxInt(Setting setting) {
long result = getMax(setting);
if (result > Integer.MAX_VALUE) {
return Integer.MAX_VALUE;
} else {
return (int) result;
}
}


private void validateHeaderTableSize(long headerTableSize) throws T {
// Need to put a sensible limit on this. Start with 16k (default is 4k)
if (headerTableSize > (16 * 1024)) {
String msg = sm.getString("connectionSettings.headerTableSizeLimit",
Long.toString(headerTableSize));
throwException(msg, Http2Error.PROTOCOL_ERROR);
}
}


private void validateEnablePush(long enablePush) throws T {
// Can't be less than zero since the result of the byte->long conversion
// will never be negative
if (enablePush > 1) {
String msg = sm.getString("connectionSettings.enablePushInvalid",
Long.toString(enablePush));
throwException(msg, Http2Error.PROTOCOL_ERROR);
}
}


private void validateInitialWindowSize(long initialWindowSize) throws T {
if (initialWindowSize > MAX_WINDOW_SIZE) {
String msg = sm.getString("connectionSettings.windowSizeTooBig",
Long.toString(initialWindowSize), Long.toString(MAX_WINDOW_SIZE));
throwException(msg, Http2Error.FLOW_CONTROL_ERROR);
}
}


private void validateMaxFrameSize(long maxFrameSize) throws T {
if (maxFrameSize < MIN_MAX_FRAME_SIZE || maxFrameSize > MAX_MAX_FRAME_SIZE) {
String msg = sm.getString("connectionSettings.maxFrameSizeInvalid",
Long.toString(maxFrameSize), Integer.toString(MIN_MAX_FRAME_SIZE),
Integer.toString(MAX_MAX_FRAME_SIZE));
throwException(msg, Http2Error.PROTOCOL_ERROR);
}
}


abstract void throwException(String msg, Http2Error error) throws T;
} }
143 changes: 12 additions & 131 deletions java/org/apache/coyote/http2/ConnectionSettingsLocal.java
Expand Up @@ -16,7 +16,6 @@
*/ */
package org.apache.coyote.http2; package org.apache.coyote.http2;


import java.util.HashMap;
import java.util.Map; import java.util.Map;


/** /**
Expand All @@ -31,92 +30,17 @@
* client will respond (almost certainly by closing the connection) as defined * client will respond (almost certainly by closing the connection) as defined
* in the HTTP/2 specification. * in the HTTP/2 specification.
*/ */
public class ConnectionSettingsLocal extends ConnectionSettingsBase { public class ConnectionSettingsLocal extends ConnectionSettingsBase<IllegalArgumentException> {


private static final Integer KEY_HEADER_TABLE_SIZE = Integer.valueOf(1); private boolean sendInProgress = false;
private static final Integer KEY_ENABLE_PUSH = Integer.valueOf(2);
private static final Integer KEY_MAX_CONCURRENT_STREAMS = Integer.valueOf(3);
private static final Integer KEY_INITIAL_WINDOW_SIZE = Integer.valueOf(4);
private static final Integer KEY_MAX_FRAME_SIZE = Integer.valueOf(5);
private static final Integer KEY_MAX_HEADER_LIST_SIZE = Integer.valueOf(6);


private static final Long DEFAULT_HEADER_TABLE_SIZE = @Override
Long.valueOf(ConnectionSettingsRemote.DEFAULT_HEADER_TABLE_SIZE); protected synchronized void set(Setting setting, Long value) {
private static final Long DEFAULT_ENABLE_PUSH = Long.valueOf(1);
private static final Long DEFAULT_MAX_CONCURRENT_STREAMS =
Long.valueOf(ConnectionSettingsRemote.UNLIMITED);
private static final Long DEFAULT_INITIAL_WINDOW_SIZE =
Long.valueOf(ConnectionSettingsRemote.DEFAULT_INITIAL_WINDOW_SIZE);
private static final Long DEFAULT_MAX_FRAME_SIZE =
Long.valueOf(ConnectionSettingsRemote.DEFAULT_MAX_FRAME_SIZE);
private static final Long DEFAULT_MAX_HEADER_LIST_SIZE =
Long.valueOf(ConnectionSettingsRemote.UNLIMITED);

boolean sendInProgress = false;

private Map<Integer,Long> current = new HashMap<>();
private Map<Integer,Long> pending = new HashMap<>();


public ConnectionSettingsLocal() {
// Set up the defaults
current.put(KEY_HEADER_TABLE_SIZE, DEFAULT_HEADER_TABLE_SIZE);
current.put(KEY_ENABLE_PUSH, DEFAULT_ENABLE_PUSH);
current.put(KEY_MAX_CONCURRENT_STREAMS, DEFAULT_MAX_CONCURRENT_STREAMS);
current.put(KEY_INITIAL_WINDOW_SIZE, DEFAULT_INITIAL_WINDOW_SIZE);
current.put(KEY_MAX_FRAME_SIZE, DEFAULT_MAX_FRAME_SIZE);
current.put(KEY_MAX_HEADER_LIST_SIZE, DEFAULT_MAX_HEADER_LIST_SIZE);
}


private synchronized void set(Integer key, long value) {
checkSend(); checkSend();
if (current.get(key).longValue() == value) { if (current.get(setting).longValue() == value.longValue()) {
pending.remove(key); pending.remove(setting);
} else {
pending.put(key, Long.valueOf(value));
}
}


private synchronized long getMin(Integer key) {
Long pendingValue = pending.get(key);
long currentValue = current.get(key).longValue();
if (pendingValue == null) {
return currentValue;
} else {
return Long.min(pendingValue.longValue(), currentValue);
}
}


private int getMinInt(Integer key) {
long result = getMin(key);
if (result > Integer.MAX_VALUE) {
return Integer.MAX_VALUE;
} else {
return (int) result;
}
}


private synchronized long getMax(Integer key) {
Long pendingValue = pending.get(key);
long currentValue = current.get(key).longValue();
if (pendingValue == null) {
return currentValue;
} else {
return Long.max(pendingValue.longValue(), currentValue);
}
}


private int getMaxInt(Integer key) {
long result = getMax(key);
if (result > Integer.MAX_VALUE) {
return Integer.MAX_VALUE;
} else { } else {
return (int) result; pending.put(setting, value);
} }
} }


Expand All @@ -132,8 +56,8 @@ synchronized byte[] getSettingsFrameForPending() {
// Stream is zero // Stream is zero
// Payload // Payload
int pos = 9; int pos = 9;
for (Map.Entry<Integer,Long> setting : pending.entrySet()) { for (Map.Entry<Setting,Long> setting : pending.entrySet()) {
ByteUtil.setTwoBytes(result, pos, setting.getKey().intValue()); ByteUtil.setTwoBytes(result, pos, setting.getKey().getId());
pos += 2; pos += 2;
ByteUtil.setFourBytes(result, pos, setting.getValue().longValue()); ByteUtil.setFourBytes(result, pos, setting.getValue().longValue());
pos += 4; pos += 4;
Expand Down Expand Up @@ -163,51 +87,8 @@ private void checkSend() {
} }




public int getHeaderTableSize() { @Override
return getMinInt(KEY_HEADER_TABLE_SIZE); void throwException(String msg, Http2Error error) throws IllegalArgumentException {
} throw new IllegalArgumentException(msg);
public void setHeaderTableSize(long headerTableSize) {
set(KEY_HEADER_TABLE_SIZE, headerTableSize);
}


public boolean getEnablePush() {
long result = getMin(KEY_ENABLE_PUSH);
return result != 0;
}
public void setEnablePush(long enablePush) {
set(KEY_ENABLE_PUSH, enablePush);
}


public long getMaxConcurrentStreams() {
return getMax(KEY_MAX_CONCURRENT_STREAMS);
}
public void setMaxConcurrentStreams(long maxConcurrentStreams) {
set(KEY_MAX_CONCURRENT_STREAMS, maxConcurrentStreams);
}


public int getInitialWindowSize() {
return getMaxInt(KEY_INITIAL_WINDOW_SIZE);
}
public void setInitialWindowSize(long initialWindowSize) {
set(KEY_INITIAL_WINDOW_SIZE, initialWindowSize);
}


public int getMaxFrameSize() {
return getMaxInt(KEY_MAX_FRAME_SIZE);
}
public void setMaxFrameSize(long maxFrameSize) {
set(KEY_MAX_FRAME_SIZE, maxFrameSize);
}


public long getMaxHeaderListSize() {
return getMax(KEY_MAX_HEADER_LIST_SIZE);
}
public void setMaxHeaderListSize(long maxHeaderListSize) {
set(KEY_MAX_HEADER_LIST_SIZE, maxHeaderListSize);
} }
} }

0 comments on commit b7110d3

Please sign in to comment.