/*
* FrameHeaderConsumer.java February 2014
*
* Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net>
*
* Licensed 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.
*/
package org.simpleframework.http.socket.service;
import java.io.IOException;
import org.simpleframework.http.socket.FrameType;
import org.simpleframework.transport.ByteCursor;
/**
* The <code>FrameHeaderConsumer</code> is used to consume frames from
* a connected TCP channel. This is a state machine that can consume
* the data one byte at a time until the entire header has been consumed.
*
* @author Niall Gallagher
*
* @see org.simpleframework.http.socket.service.FrameConsumer
*/
class FrameHeaderConsumer implements FrameHeader {
/**
* This is the frame type which represents the opcode.
*/
private FrameType type;
/**
* If header consumed was from a client frame the data is masked.
*/
private boolean masked;
/**
* Determines if this frame is part of a larger sequence.
*/
private boolean last;
/**
* This is the mask that is used to obfuscate client frames.
*/
private byte[] mask;
/**
* This is the octet that is used to read one byte at a time.
*/
private byte[] octet;
/**
* Required number of bytes within the frame header.
*/
private int required;
/**
* This represents the length of the frame payload.
*/
private int length;
/**
* This determines the count of the mask bytes read.
*/
private int count;
/**
* Constructor for the <code>FrameHeaderConsumer</code> object. This
* is used to create a consumer to read the bytes that form the
* frame header from an underlying TCP connection.
*/
public FrameHeaderConsumer() {
this.octet = new byte[1];
this.mask = new byte[4];
this.length = -1;
}
/**
* This provides the length of the payload within the frame. It
* is used to determine how much data to consume from the underlying
* TCP stream in order to recreate the frame to dispatch.
*
* @return the number of bytes used in the frame
*/
public int getLength() {
return length;
}
/**
* This provides the client mask send with the request. The mask is
* a 32 bit value that is used as an XOR bitmask of the client
* payload. Masking applies only in the client to server direction.
*
* @return this returns the 32 bit mask used for this frame
*/
public byte[] getMask() {
return mask;
}
/**
* This is used to determine the type of frame. Interpretation of
* this type is outlined in RFC 6455 and can be loosely categorised
* as control frames and either data or binary frames.
*
* @return this returns the type of frame that this represents
*/
public FrameType getType() {
return type;
}
/**
* This is used to determine if the frame is masked. All client
* frames should be masked according to RFC 6455. If masked the
* payload will have its contents bitmasked with a 32 bit value.
*
* @return this returns true if the payload has been masked
*/
public boolean isMasked() {
return masked;
}
/**
* This is used to determine if the frame is the final frame in
* a sequence of fragments or a whole frame. If this returns false
* then the frame is a continuation from from a sequence of
* fragments, otherwise it is a whole frame or the last fragment.
*
* @return this returns false if the frame is a fragment
*/
public boolean isFinal() {
return last;
}
/**
* This consumes frame bytes using the provided cursor. The consumer
* acts as a state machine by consuming the data as that data
* becomes available, this allows it to consume data asynchronously
* and dispatch once the whole frame has been consumed.
*
* @param cursor the cursor to consume the frame data from
*/
public void consume(ByteCursor cursor) throws IOException {
if (cursor.isReady()) {
if (type == null) {
int count = cursor.read(octet);
if (count <= 0) {
throw new IOException("Ready cursor produced no data");
}
type = FrameType.resolveType(octet[0] & 0x0f);
if(type == null) {
throw new IOException("Frame type code not supported");
}
last = (octet[0] & 0x80) != 0;
} else {
if (length < 0) {
int count = cursor.read(octet);
if (count <= 0) {
throw new IOException("Ready cursor produced no data");
}
masked = (octet[0] & 0x80) != 0;
length = (octet[0] & 0x7F);
if (length == 0x7F) { // 8 byte extended payload length
required = 8;
length = 0;
} else if (length == 0x7E) { // 2 bytes extended payload length
required = 2;
length = 0;
}
} else if (required > 0) {
int count = cursor.read(octet);
if (count == -1) {
throw new IOException("Could not read length");
}
length |= (octet[0] & 0xFF) << (8 * --required);
} else {
if (masked && count < mask.length) {
int size = cursor.read(mask, count, mask.length - count);
if (size == -1) {
throw new IOException("Could not read mask");
}
count += size;
}
}
}
}
}
/**
* This is used to determine if the collector has finished. If it
* is not finished the collector will be registered to listen for
* an I/O intrrupt to read further bytes of the frame.
*
* @return true if the collector has finished consuming
*/
public boolean isFinished() {
if(type != null) {
if(length >= 0 && required == 0) {
if(masked) {
return count == mask.length;
}
return true;
}
}
return false;
}
/**
* This resets the collector to its original state so that it can
* be reused. Reusing the collector has obvious benefits as it will
* reduce the amount of memory churn for the server.
*/
public void clear() {
type = null;
length = -1;
required = 0;
masked = false;
count = 0;
}
}