in src/main/java/com/twitter/whiskey/net/SpdyFrameDecoder.java [105:388]
public void decode(ByteBuffer buffer) {
boolean last;
int statusCode;
while (true) {
switch(state) {
case READ_COMMON_HEADER:
if (buffer.remaining() < SPDY_HEADER_SIZE) {
return;
}
int frameOffset = buffer.position();
int flagsOffset = frameOffset + SPDY_HEADER_FLAGS_OFFSET;
int lengthOffset = frameOffset + SPDY_HEADER_LENGTH_OFFSET;
buffer.position(frameOffset + SPDY_HEADER_SIZE);
boolean control = (buffer.get(frameOffset) & 0x80) != 0;
int version;
int type;
if (control) {
// Decode control frame common header
version = getUnsignedShort(buffer, frameOffset) & 0x7FFF;
type = getUnsignedShort(buffer, frameOffset + SPDY_HEADER_TYPE_OFFSET);
streamId = SPDY_SESSION_STREAM_ID; // Default to session Stream-ID
} else {
// Decode data frame common header
version = spdyVersion; // Default to expected version
type = SPDY_DATA_FRAME;
streamId = getUnsignedInt(buffer, frameOffset);
}
flags = buffer.get(flagsOffset);
length = getUnsignedMedium(buffer, lengthOffset);
// Check version first then validity
if (version != spdyVersion) {
state = State.FRAME_ERROR;
delegate.readFrameError("Invalid SPDY Version");
} else if (!isValidFrameHeader(streamId, type, flags, length)) {
state = State.FRAME_ERROR;
delegate.readFrameError("Invalid Frame Error");
} else {
state = getNextState(type, length);
}
break;
case READ_DATA_FRAME:
if (length == 0) {
state = State.READ_COMMON_HEADER;
delegate.readDataFrame(streamId, hasFlag(flags, SPDY_DATA_FLAG_FIN), ByteBuffer.allocate(0));
break;
}
// Generate data frames that do not exceed maxChunkSize and generally exceed
// minChunkSize
int bytesToRead = Math.min(maxChunkSize, length);
int bytesBuffered = buffer.remaining();
if (bytesBuffered < bytesToRead) {
if (bytesBuffered < minChunkSize) return;
bytesToRead = bytesBuffered;
}
ByteBuffer data = ByteBuffer.allocate(bytesToRead);
int oldLimit = buffer.limit();
buffer.limit(buffer.position() + bytesToRead);
data.put(buffer);
data.flip();
buffer.limit(oldLimit);
length -= bytesToRead;
if (length == 0) {
state = State.READ_COMMON_HEADER;
}
last = length == 0 && hasFlag(flags, SPDY_DATA_FLAG_FIN);
delegate.readDataFrame(streamId, last, data);
break;
case READ_SYN_STREAM_FRAME:
if (buffer.remaining() < 10) {
return;
}
int offset = buffer.position();
streamId = getUnsignedInt(buffer, offset);
int associatedToStreamId = getUnsignedInt(buffer, offset + 4);
byte priority = (byte) (buffer.get(offset + 8) >> 5 & 0x07);
last = hasFlag(flags, SPDY_FLAG_FIN);
boolean unidirectional = hasFlag(flags, SPDY_FLAG_UNIDIRECTIONAL);
buffer.position(offset + 10);
length -= 10;
if (streamId == 0) {
state = State.FRAME_ERROR;
delegate.readFrameError("Invalid SYN_STREAM Frame");
} else {
state = State.READ_HEADER_BLOCK;
delegate.readSynStreamFrame(streamId, associatedToStreamId, priority, last, unidirectional);
}
break;
case READ_SYN_REPLY_FRAME:
if (buffer.remaining() < 4) {
return;
}
streamId = getUnsignedInt(buffer);
last = hasFlag(flags, SPDY_FLAG_FIN);
length -= 4;
if (streamId == 0) {
state = State.FRAME_ERROR;
delegate.readFrameError("Invalid SYN_REPLY Frame");
} else {
state = State.READ_HEADER_BLOCK;
delegate.readSynReplyFrame(streamId, last);
}
break;
case READ_RST_STREAM_FRAME:
if (buffer.remaining() < 8) {
return;
}
streamId = getUnsignedInt(buffer);
statusCode = buffer.getInt();
if (streamId == 0 || statusCode == 0) {
state = State.FRAME_ERROR;
delegate.readFrameError("Invalid RST_STREAM Frame");
} else {
state = State.READ_COMMON_HEADER;
delegate.readRstStreamFrame(streamId, statusCode);
}
break;
case READ_SETTINGS_FRAME:
if (buffer.remaining() < 4) {
return;
}
boolean clear = hasFlag(flags, SPDY_SETTINGS_CLEAR);
numSettings = getUnsignedInt(buffer);
length -= 4;
// Validate frame length against number of entries. Each ID/Value entry is 8 bytes.
if ((length & 0x07) != 0 || length >> 3 != numSettings) {
state = State.FRAME_ERROR;
delegate.readFrameError("Invalid SETTINGS Frame");
} else {
state = State.READ_SETTING;
delegate.readSettingsFrame(clear);
}
break;
case READ_SETTING:
if (numSettings == 0) {
state = State.READ_COMMON_HEADER;
delegate.readSettingsEnd();
break;
}
if (buffer.remaining() < 8) {
return;
}
byte settingsFlags = buffer.get();
int id = getUnsignedMedium(buffer);
int value = buffer.getInt();
boolean persistValue = hasFlag(settingsFlags, SPDY_SETTINGS_PERSIST_VALUE);
boolean persisted = hasFlag(settingsFlags, SPDY_SETTINGS_PERSISTED);
--numSettings;
delegate.readSetting(id, value, persistValue, persisted);
break;
case READ_PING_FRAME:
if (buffer.remaining() < 4) {
return;
}
int pingId = buffer.getInt();
state = State.READ_COMMON_HEADER;
delegate.readPingFrame(pingId);
break;
case READ_GOAWAY_FRAME:
if (buffer.remaining() < 8) {
return;
}
int lastGoodStreamId = getUnsignedInt(buffer);
statusCode = buffer.getInt();
state = State.READ_COMMON_HEADER;
delegate.readGoAwayFrame(lastGoodStreamId, statusCode);
break;
case READ_HEADERS_FRAME:
if (buffer.remaining() < 4) {
return;
}
streamId = getUnsignedInt(buffer);
last = hasFlag(flags, SPDY_FLAG_FIN);
length -= 4;
if (streamId == 0) {
state = State.FRAME_ERROR;
delegate.readFrameError("Invalid HEADERS Frame");
} else {
state = State.READ_HEADER_BLOCK;
delegate.readHeadersFrame(streamId, last);
}
break;
case READ_WINDOW_UPDATE_FRAME:
if (buffer.remaining() < 8) {
return;
}
streamId = getUnsignedInt(buffer);
int deltaWindowSize = getUnsignedInt(buffer);
if (deltaWindowSize == 0) {
state = State.FRAME_ERROR;
delegate.readFrameError("Invalid WINDOW_UPDATE Frame");
} else {
state = State.READ_COMMON_HEADER;
delegate.readWindowUpdateFrame(streamId, deltaWindowSize);
}
break;
case READ_HEADER_BLOCK:
if (length == 0) {
state = State.READ_COMMON_HEADER;
headerBlockDecoder.endHeaderBlock();
delegate.readHeadersEnd(streamId);
break;
}
if (!buffer.hasRemaining()) {
return;
}
int headerBytes = Math.min(buffer.remaining(), length);
ByteBuffer headerBlock = buffer.slice();
headerBlock.limit(headerBytes);
try {
headerBlockDecoder.decode(headerBlock, streamId);
} catch (Exception e) {
state = State.FRAME_ERROR;
}
int bytesRead = headerBytes - headerBlock.remaining();
buffer.position(buffer.position() + bytesRead);
length -= bytesRead;
break;
case DISCARD_FRAME:
int numBytes = Math.min(buffer.remaining(), length);
buffer.position(buffer.position() + numBytes);
length -= numBytes;
if (length == 0) {
state = State.READ_COMMON_HEADER;
break;
}
return;
case FRAME_ERROR:
buffer.position(buffer.limit());
return;
default:
throw new Error("Shouldn't reach here.");
}
}
}