/*
* FrameConsumer.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.Frame;
import org.simpleframework.http.socket.FrameType;
import org.simpleframework.transport.ByteCursor;
/**
* The <code>FrameConsumer</code> object is used to read a WebSocket
* frame as defined by RFC 6455. This is a state machine that can read
* the data one byte at a time until the entire frame has been consumed.
*
* @author Niall Gallagher
*
* @see org.simpleframework.http.socket.service.FrameCollector
*/
class FrameConsumer {
/**
* This is used to consume the header part of the frame.
*/
private FrameHeaderConsumer header;
/**
* This is used to interpret the header and create a frame.
*/
private FrameBuilder builder;
/**
* This is used to buffer the bytes that form the frame.
*/
private byte[] buffer;
/**
* This is a count of the payload bytes currently consumed.
*/
private int count;
/**
* Constructor for the <code>FrameConsumer</code> object. This is
* used to create a consumer to read the bytes that form the frame
* from an underlying TCP connection. Internally a buffer is created
* to allow bytes to be consumed and collected in chunks.
*/
public FrameConsumer() {
this.header = new FrameHeaderConsumer();
this.builder = new FrameBuilder(header);
this.buffer = new byte[2048];
}
/**
* 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 header.getType();
}
/**
* This is used to create a frame object to represent the data that
* has been consumed. The frame created will make a copy of the
* internal byte buffer so this method should be used sparingly.
*
* @return this returns a frame created from the consumed bytes
*/
public Frame getFrame() {
return builder.create(buffer, count);
}
/**
* 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 {
while (cursor.isReady()) {
if(!header.isFinished()) {
header.consume(cursor);
}
if(header.isFinished()) {
int length = header.getLength();
if(count <= length) {
if(buffer.length < length) {
buffer = new byte[length];
}
if(count < length) {
int size = cursor.read(buffer, count, length - count);
if(size == -1) {
throw new IOException("Could only read " + count + " of length " + length);
}
count += size;
}
if(count == length) {
if(header.isMasked()) {
byte[] mask = header.getMask();
for (int i = 0; i < count; i++) {
buffer[i] ^= mask[i % 4];
}
}
break;
}
}
}
}
}
/**
* 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 interrupt to read further bytes of the frame.
*
* @return true if the collector has finished consuming
*/
public boolean isFinished() {
if(header.isFinished()) {
int length = header.getLength();
if(count == 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() {
header.clear();
count = 0;
}
}