package org.glassfish.grizzly.http2.frames;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.glassfish.grizzly.Buffer;
import org.glassfish.grizzly.Grizzly;
import org.glassfish.grizzly.ThreadCache;
import org.glassfish.grizzly.http.util.BufferChunk;
import org.glassfish.grizzly.http.util.ByteChunk;
import org.glassfish.grizzly.http.util.DataChunk;
import org.glassfish.grizzly.memory.MemoryManager;
public class SettingsFrame extends Http2Frame {
private static final Logger LOGGER = Grizzly.logger(SettingsFrame.class);
private static final char[] CA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".toCharArray();
private static final int[] IA = new int[256];
static {
Arrays.fill(IA, -1);
for (int i = 0, iS = CA.length; i < iS; i++) {
IA[CA[i]] = i;
}
}
private static final ThreadCache.CachedTypeIndex<SettingsFrame> CACHE_IDX = ThreadCache.obtainIndex(SettingsFrame.class, 8);
private static final String[] OPTION_TEXT = { "HEADER_TABLE_SIZE", "ENABLE_PUSH", "MAX_CONCURRENT_STREAMS", "INITIAL_WINDOW_SIZE", "MAX_FRAME_SIZE",
"MAX_HEADER_LIST_SIZE" };
public static final int TYPE = 4;
public static final byte ACK_FLAG = 0x01;
static final Map<Integer, String> FLAG_NAMES_MAP = new HashMap<>(2);
static {
FLAG_NAMES_MAP.put((int) ACK_FLAG, "ACK");
}
public static final int MAX_DEFINED_SETTINGS = 6;
public static final int = 1;
public static final int SETTINGS_ENABLE_PUSH = 2;
public static final int SETTINGS_MAX_CONCURRENT_STREAMS = 3;
public static final int SETTINGS_INITIAL_WINDOW_SIZE = 4;
public static final int SETTINGS_MAX_FRAME_SIZE = 5;
public static final int = 6;
private int numberOfSettings;
private final Setting[] settings = new Setting[MAX_DEFINED_SETTINGS];
private SettingsFrame() {
for (int i = 0; i < MAX_DEFINED_SETTINGS; i++) {
settings[i] = new Setting();
}
}
static SettingsFrame create() {
SettingsFrame frame = ThreadCache.takeFromCache(CACHE_IDX);
if (frame == null) {
frame = new SettingsFrame();
}
return frame;
}
public static SettingsFrame fromBuffer(final int flags, final int streamId, final Buffer frameBuffer) {
SettingsFrame frame = create();
frame.setStreamId(streamId);
frame.setFlags(flags);
frame.setFrameBuffer(frameBuffer);
if (frameBuffer.remaining() % 6 == 0) {
while (frameBuffer.hasRemaining()) {
frame.addSetting(frameBuffer.getShort(), frameBuffer.getInt());
}
} else {
frame.numberOfSettings = -1;
}
return frame;
}
@Override
public int getType() {
return TYPE;
}
public static SettingsFrame fromBase64Uri(final DataChunk src) {
if (src.getType() == DataChunk.Type.Bytes) {
final ByteChunk bc = src.getByteChunk();
return parseBase64Uri(bc.getBuffer(), bc.getStart(), bc.getEnd());
} else if (src.getType() == DataChunk.Type.Buffer) {
final BufferChunk bc = src.getBufferChunk();
return parseBase64Uri(bc.getBuffer(), bc.getStart(), bc.getEnd());
}
return parseBase64Uri(src.toString());
}
private static SettingsFrame parseBase64Uri(final byte[] bytes, int offs, final int end) {
final SettingsFrame frame = new SettingsFrame();
while (offs < end) {
frame.addBase64UriSetting(IA[bytes[offs++]], IA[bytes[offs++]], IA[bytes[offs++]], IA[bytes[offs++]], IA[bytes[offs++]], IA[bytes[offs++]],
IA[bytes[offs++]], IA[bytes[offs++]]);
}
return frame;
}
private static SettingsFrame parseBase64Uri(final Buffer buffer, int offs, final int end) {
final SettingsFrame frame = new SettingsFrame();
while (offs < end) {
frame.addBase64UriSetting(IA[buffer.get(offs++)], IA[buffer.get(offs++)], IA[buffer.get(offs++)], IA[buffer.get(offs++)], IA[buffer.get(offs++)],
IA[buffer.get(offs++)], IA[buffer.get(offs++)], IA[buffer.get(offs++)]);
}
return frame;
}
private static SettingsFrame parseBase64Uri(final String s) {
final SettingsFrame frame = new SettingsFrame();
int offs = 0;
final int end = s.length();
while (offs < end) {
frame.addBase64UriSetting(IA[s.charAt(offs++)], IA[s.charAt(offs++)], IA[s.charAt(offs++)], IA[s.charAt(offs++)], IA[s.charAt(offs++)],
IA[s.charAt(offs++)], IA[s.charAt(offs++)], IA[s.charAt(offs++)]);
}
return frame;
}
public static SettingsFrameBuilder builder() {
return new SettingsFrameBuilder();
}
public boolean isAck() {
return isFlagSet(ACK_FLAG);
}
public int getNumberOfSettings() {
return numberOfSettings;
}
public Setting getSettingByIndex(final int idx) {
return idx >= 0 && idx < numberOfSettings ? settings[idx] : null;
}
public String toBase64Uri() {
if (numberOfSettings == 0) {
return "";
}
final StringBuilder sb = new StringBuilder(numberOfSettings * 8);
for (int i = 0; i < numberOfSettings; i++) {
final int id = settings[i].id;
final int value = settings[i].value;
threeBytesToBase64Uri(id >> 8 & 0xFF, id & 0xFF, value >>> 24, sb);
threeBytesToBase64Uri(value >> 16 & 0xFF, value >> 8 & 0xFF, value & 0xFF, sb);
}
return sb.toString();
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("SettingsFrame {").append(headerToString()).append(", numberOfSettings=").append(numberOfSettings);
if (numberOfSettings > 0) {
sb.append(", [");
for (int i = 0; i < numberOfSettings; i++) {
sb.append(' ');
sb.append(OPTION_TEXT[settings[i].id - 1]).append('=').append(settings[i].value);
}
}
sb.append(" ]}");
return sb.toString();
}
@Override
protected int calcLength() {
if (numberOfSettings == -1) {
return frameBuffer.remaining();
}
return numberOfSettings * 6;
}
public String getSettingNameById(final int id) {
return OPTION_TEXT[id - 1];
}
@Override
public void recycle() {
if (DONT_RECYCLE) {
return;
}
numberOfSettings = 0;
super.recycle();
ThreadCache.putToCache(CACHE_IDX, this);
}
@Override
public Buffer toBuffer(final MemoryManager memoryManager) {
final Buffer buffer = memoryManager.allocate(FRAME_HEADER_SIZE + numberOfSettings * 6);
serializeFrameHeader(buffer);
if (numberOfSettings > 0) {
for (int i = 0; i < numberOfSettings; i++) {
final Setting setting = settings[i];
buffer.putShort((short) setting.id);
buffer.putInt(setting.value);
}
}
buffer.trim();
return buffer;
}
@Override
protected Map<Integer, String> getFlagNamesMap() {
return FLAG_NAMES_MAP;
}
private void threeBytesToBase64Uri(final int b1, final int b2, final int b3, final StringBuilder to) {
to.append(CA[b1 >>> 2]).append(CA[(b1 & 3) << 4 | b2 >>> 4]).append(CA[(b2 & 15) << 2 | b3 >>> 6]).append(CA[b3 & 63]);
}
private void addBase64UriSetting(final int b1, final int b2, final int b3, final int b4, final int b5, final int b6, final int b7, final int b8) {
if (b1 == -1 || b2 == -1 || b3 == -1 || b4 == -1 || b5 == -1 || b6 == -1 || b7 == -1 || b8 == -1) {
throw new IllegalStateException("Unknown base64uri character");
}
final int tmp1 = b1 << 18 | b2 << 12 | b3 << 6 | b4;
final int tmp2 = b5 << 18 | b6 << 12 | b7 << 6 | b8;
final int setting = tmp1 >> 8;
final int value = (tmp1 & 0xFF) << 24 | tmp2;
addSetting(setting, value);
}
private void addSetting(final int settingId, final int value) {
if (settingId > 0 && settingId <= MAX_DEFINED_SETTINGS) {
final int oldIdx = idx(settingId);
if (oldIdx != -1) {
numberOfSettings--;
if (oldIdx < numberOfSettings) {
final Setting oldSetting = settings[oldIdx];
System.arraycopy(settings, oldIdx + 1, settings, oldIdx, numberOfSettings - oldIdx);
settings[numberOfSettings] = oldSetting;
}
}
final Setting storedSetting = settings[numberOfSettings++];
storedSetting.id = settingId;
storedSetting.value = value;
} else {
LOGGER.log(Level.WARNING, "Setting {0} is unknown and will be ignored", settingId);
}
}
private int idx(final int settingId) {
for (int i = 0; i < numberOfSettings; i++) {
if (settings[i].id == settingId) {
return i;
}
}
return -1;
}
public static class SettingsFrameBuilder extends Http2FrameBuilder<SettingsFrameBuilder> {
private int numberOfSettings;
private final Setting[] settings = new Setting[MAX_DEFINED_SETTINGS];
protected SettingsFrameBuilder() {
}
public SettingsFrameBuilder setting(final int settingId, final int value) {
if (settingId > 0 && settingId <= MAX_DEFINED_SETTINGS) {
final Setting settingContainer;
final int oldIdx = idx(settingId);
if (oldIdx != -1) {
numberOfSettings--;
if (oldIdx < numberOfSettings) {
final Setting oldSetting = settings[oldIdx];
System.arraycopy(settings, oldIdx + 1, settings, oldIdx, numberOfSettings - oldIdx);
settings[numberOfSettings++] = oldSetting;
settingContainer = oldSetting;
} else {
settingContainer = settings[numberOfSettings++];
}
} else {
settingContainer = new Setting();
settings[numberOfSettings++] = settingContainer;
}
settingContainer.id = settingId;
settingContainer.value = value;
} else {
LOGGER.log(Level.WARNING, "Setting {0} is unknown and will be ignored", settingId);
}
return this;
}
public SettingsFrameBuilder setAck() {
setFlag(ACK_FLAG);
return this;
}
@Override
public SettingsFrame build() {
final SettingsFrame frame = SettingsFrame.create();
setHeaderValuesTo(frame);
for (int i = 0; i < numberOfSettings; i++) {
frame.addSetting(settings[i].id, settings[i].value);
}
return frame;
}
private int idx(final int settingId) {
for (int i = 0; i < numberOfSettings; i++) {
if (settings[i].id == settingId) {
return i;
}
}
return -1;
}
@Override
protected SettingsFrameBuilder getThis() {
return this;
}
}
public static final class Setting {
private int id;
private int value;
private Setting() {
}
public int getId() {
return id;
}
public int getValue() {
return value;
}
}
}