org.beepcore.beep.core.ChannelImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.beepcore.beep.core.ChannelImpl.java

Source

/*
 * ChannelImpl.java  $Revision: 1.12 $ $Date: 2006/02/25 17:48:37 $
 *
 * Copyright (c) 2001 Invisible Worlds, Inc.  All rights reserved.
 * Copyright (c) 2001-2003 Huston Franklin.  All rights reserved.
 *
 * The contents of this file are subject to the Blocks Public License (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.beepcore.org/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied.  See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * Source code in 3rd-party is licensed and owned by their respective
 * copyright holders.
 *
 * All other source code is copyright Tresys Technology and licensed as below.
 *
 * Copyright (c) 2012 Tresys Technology LLC, Columbia, Maryland, USA
 *
 * This software was developed by Tresys Technology LLC
 * with U.S. Government sponsorship.
 *
 * 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.beepcore.beep.core;

import java.util.*;

import EDU.oswego.cs.dl.util.concurrent.PooledExecutor;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.beepcore.beep.util.BufferSegment;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * ChannelImpl is a conduit for a certain kind of traffic over a session,
 * determined by the profile of the channel.  Channels are created by Session
 *
 * @author Eric Dixon
 * @author Huston Franklin
 * @author Jay Kint
 * @author Scott Pead
 * @version $Revision: 1.12 $, $Date: 2006/02/25 17:48:37 $
 *
 */
class ChannelImpl implements Channel, Runnable {

    // class variables
    private static final BufferSegment zeroLengthSegment = new BufferSegment(new byte[0]);

    private static final PooledExecutor callbackQueue = new PooledExecutor();

    /** @todo check this */

    // default values for some variables
    static final int DEFAULT_WINDOW_SIZE = 4096;

    static final RequestHandler defaultHandler = new DefaultMSGHandler();

    // instance variables

    private Log log = LogFactory.getLog(this.getClass());

    /** syntax of messages */
    private String profile;

    /** encoding of used by profile */
    private String encoding;

    /** channel number on the session */
    private String number;

    /** Used to pass data sent on the Start Channel request */
    private String startData;

    /** receiver of MSG messages */
    private RequestHandler handler;

    /** number of last message sent */
    private int lastMessageSent;

    /** sequence number for messages sent */
    private long sentSequence;

    /** sequence for messages received */
    private long recvSequence;

    /** messages waiting for replies */
    private List sentMSGQueue;

    /** MSG we've received by awaiting proceesing of a former MSG */
    private LinkedList recvMSGQueue;

    /** messages queued to be sent */
    private LinkedList pendingSendMessages;

    /** session this channel sends through. */
    SessionImpl session;

    /** message that we are receiving frames */
    private LinkedList recvReplyQueue;

    private int state = STATE_INITIALIZED;

    private Frame previousFrame;

    /** size of the peer's receive buffer */
    private AtomicInteger peerWindowSize;

    /** size of the receive buffer */
    private AtomicInteger recvWindowSize;

    /** amount of the buffer in use */
    private AtomicInteger recvWindowUsed;

    private AtomicInteger recvWindowFreed;

    private Object applicationData = null;

    // tuningProfile indicates that the profile for this channel will
    // request a tuning reset
    private boolean tuningProfile = false;

    /* (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    public String toString() {
        return super.toString() + " (#" + getNumberAsString() + " " + getStateString() + " on " + session.toString()
                + ")";
    }

    ChannelImpl(String profile, String number, RequestHandler handler, boolean tuningReset, SessionImpl session) {
        this.profile = profile;
        this.encoding = Constants.ENCODING_DEFAULT;
        this.number = number;
        this.setRequestHandler(handler, tuningReset);
        this.session = session;
        sentSequence = 0;
        recvSequence = 0;
        lastMessageSent = 1;

        pendingSendMessages = new LinkedList();
        sentMSGQueue = Collections.synchronizedList(new LinkedList());
        recvMSGQueue = new LinkedList();
        recvReplyQueue = new LinkedList();
        state = STATE_INITIALIZED;
        recvWindowUsed = new AtomicInteger(0);
        recvWindowFreed = new AtomicInteger(0);
        recvWindowSize = new AtomicInteger(DEFAULT_WINDOW_SIZE);
        peerWindowSize = new AtomicInteger(DEFAULT_WINDOW_SIZE);
    }

    ChannelImpl(String profile, String number, SessionImpl session) {
        this(profile, number, defaultHandler, false, session);
    }

    static ChannelImpl createChannelZero(SessionImpl session, ReplyListener reply, RequestHandler handler) {
        ChannelImpl channel = new ChannelImpl(null, "0", handler, true, session);

        // Add a MSG to the SentMSGQueue to fake channel into accepting the
        // greeting which comes in an unsolicited RPY.
        channel.sentMSGQueue.add(new MessageStatus(channel, Message.MESSAGE_TYPE_MSG, 0, null, reply));
        channel.recvMSGQueue.add(new MessageMSGImpl(channel, 0, null));

        channel.state = STATE_ACTIVE;

        return channel;
    }

    /**
     * Closes the channel.
     *
     * @throws BEEPException
     */
    public void close() throws BEEPException {

        // @todo the other BEEP peer may refuse this request
        // should we return a boolean or throw a CloseChannelException?
        session.closeChannel(this, BEEPError.CODE_SUCCESS, null);
    }

    // instance methods

    /**
     * Returns application context data previously set using
     * <code>setAppData()</code>.
     *
     * @see #setAppData
     */
    public Object getAppData() {
        return this.applicationData;
    }

    /**
     * Set the application context data member for future retrieval.
     *
     * @see #getAppData
     */
    public void setAppData(Object applicationData) {
        this.applicationData = applicationData;
    }

    /**
     * Returns the receive buffer size for this channel.
     */
    public synchronized int getBufferSize() {
        return recvWindowSize.intValue();
    }

    /**
     * Returns the encoding used on this <code>Channel</code>
     * @todo look at removing this and adding the information to getProfile()
     */
    String getEncoding() {
        return encoding;
    }

    void setEncoding(String enc) {
        this.encoding = enc;
    }

    /**
     * Return the number of this <code>Channel</code>.
     *
     */
    public int getNumber() {
        return Integer.parseInt(number);
    }

    /**
     * Sets the receive buffer size for this channel.  Default size is 4K.
     *
     *
     * @param size
     *
     * @throws BEEPException
     *
     */
    public void setReceiveBufferSize(int size) throws BEEPException {
        synchronized (this) {
            if ((state != STATE_ACTIVE) && (state != STATE_INITIALIZED)) {
                throw new BEEPException("Channel in a bad state.");
            }

            // make sure we aren't setting the size less than what is currently
            // in the buffer right now.
            if (size < recvWindowUsed.intValue()) {
                throw new BEEPException("New size is less than what is " + "currently in use.");
            }

            // set the new size and copy the buffer
            recvWindowSize.set(size);

            if (log.isDebugEnabled()) {
                log.debug("Buffer size for channel " + number + " set to " + recvWindowSize);
            }

            sendWindowUpdate();
        }
    }

    /**
     * Returns the <code>RequestHandler</code> registered with this channel.
     */
    public RequestHandler getRequestHandler() {
        return this.handler;
    }

    /**
     * Sets the MSG handler for this <code>Channel</code>.
     * 
     * @param handler <code>RequestHandler</code> to handle received MSG messages.
     * @return The previous <code>RequestHandler</code> or <code>null</code> if
     *         one wasn't set.
     */
    public RequestHandler setRequestHandler(RequestHandler handler) {
        return this.setRequestHandler(handler, false);
    }

    /**
     * Sets the MSG handler for this <code>Channel</code>.
     * 
     * @param handler <code>RequestHandler</code> to handle received MSG messages.
     * @param tuningReset flag indicating that the profile will request a
     *                    tuning reset.
     * @return The previous <code>RequestHandler</code> or <code>null</code> if
     *         one wasn't set.
     */
    public RequestHandler setRequestHandler(RequestHandler handler, boolean tuningReset) {
        RequestHandler tmp = this.handler;

        this.handler = handler;
        this.tuningProfile = tuningReset;

        return tmp;
    }

    /**
     * Returns the session for this channel.
     *
     */
    public Session getSession() {
        return this.session;
    }

    public void run() {
        MessageMSGImpl m;
        synchronized (recvMSGQueue) {
            m = (MessageMSGImpl) recvMSGQueue.getFirst();
            synchronized (m) {
                m.setNotified();
            }
        }

        handler.receiveMSG(m);
    }

    /**
     * Sends a message of type MSG.
     *
     * @param stream Data to send in the form of <code>DataStream</code>.
     * @param replyListener A "one-shot" listener that will handle replies
     * to this sendMSG listener.
     *
     * @see OutputDataStream
     * @see MessageStatus
     *
     * @return MessageStatus
     *
     * @throws BEEPException if an error is encoutered.
     */
    public MessageStatus sendMSG(OutputDataStream stream, ReplyListener replyListener) throws BEEPException {
        MessageStatus status;

        if (state != STATE_ACTIVE && state != STATE_TUNING) {
            switch (state) {
            case STATE_INITIALIZED:
                throw new BEEPException("Channel is uninitialised.");
            default:
                throw new BEEPException("Channel is in an unknown state.");
            }
        }

        synchronized (this) {

            // create a new request
            status = new MessageStatus(this, Message.MESSAGE_TYPE_MSG, lastMessageSent, stream, replyListener);

            // message 0 was the greeting, it was already sent, inc the counter
            ++lastMessageSent;

            // put this in the list of messages waiting
            // may want to put an expiration or something in here so they
            // don't just stay around taking up space.
            // @todo it's a synchronized list, you don't have to sync
            synchronized (sentMSGQueue) {
                sentMSGQueue.add(status);
            }
            // send it on the session
            sendToPeer(status);
        }

        return status;
    }

    void abort() {
        setState(ChannelImpl.STATE_ABORTED);
    }

    void addPiggybackedMSG(PiggybackedMSG msg) throws BEEPException {
        recvMSGQueue.add(msg);
        try {
            callbackQueue.execute(this);
        } catch (InterruptedException e) {
            /** @TODO handle this better */
            throw new BEEPException(e);
        }
    }

    /**
     * get the number of this <code>Channel</code> as a <code>String</code>
     *
     */
    String getNumberAsString() {
        return number;
    }

    public int getState() {
        return state;
    }

    private String getStateString() {
        switch (state) {
        case STATE_INITIALIZED:
            return "initialized";
        case STATE_STARTING:
            return "starting";
        case STATE_ACTIVE:
            return "active";
        case STATE_TUNING_PENDING:
            return "tuning pending";
        case STATE_TUNING:
            return "tuning";
        case STATE_CLOSE_PENDING:
            return "close pending";
        case STATE_CLOSING:
            return "closing";
        case STATE_CLOSED:
            return "closed";
        case STATE_ABORTED:
            return "aborted";
        default:
            return "unknown";
        }
    }

    private void receiveFrame(Frame frame) throws BEEPException {

        // if this is an incoming message rather than a reply to a
        // previously sent message
        if (frame.getMessageType() == Message.MESSAGE_TYPE_MSG) {
            boolean notify = false;

            synchronized (recvMSGQueue) {
                MessageMSGImpl m = null;
                if (recvMSGQueue.size() != 0) {
                    m = (MessageMSGImpl) recvMSGQueue.getLast();

                    if (m.getMsgno() != frame.getMsgno()) {
                        m = null;
                    }
                }

                if (m != null) {
                    /// Move this code to DataStream...
                    Iterator i = frame.getPayload();
                    synchronized (m) {
                        while (i.hasNext()) {
                            m.getDataStream().add((BufferSegment) i.next());
                        }

                        if (frame.isLast()) {
                            m.getDataStream().setComplete();
                        }
                    }

                    return;
                }

                m = new MessageMSGImpl(this, frame.getMsgno(), new InputDataStream(this));

                m.setNotified();

                Iterator i = frame.getPayload();
                while (i.hasNext()) {
                    m.getDataStream().add((BufferSegment) i.next());
                }

                if (frame.isLast()) {
                    m.getDataStream().setComplete();
                }

                recvMSGQueue.addLast(m);

                if (recvMSGQueue.size() == 1) {
                    try {
                        callbackQueue.execute(this);
                    } catch (InterruptedException e) {
                        /** @TODO handle this better */
                        throw new BEEPException(e);
                    }
                }
            }

            return;
        }

        MessageImpl m = null;

        // This frame must be for a reply (RPY, ERR, ANS, NUL)
        MessageStatus mstatus;

        // Find corresponding MSG for this reply
        synchronized (sentMSGQueue) {
            Message sentMSG;

            if (sentMSGQueue.size() == 0) {

                // @todo shutdown session (we think)
            }

            mstatus = (MessageStatus) sentMSGQueue.get(0);

            if (mstatus.getMsgno() != frame.getMsgno()) {

                // @todo shutdown session (we think)
            }

            // If this is the last frame for the reply (NUL, RPY, or
            // ERR) to this MSG.
            if ((frame.isLast() == true) && (frame.getMessageType() != Message.MESSAGE_TYPE_ANS)) {
                sentMSGQueue.remove(0);
            }
        }

        ReplyListener replyListener = mstatus.getReplyListener();

        // error if they don't have either a frame or reply listener
        if (replyListener == null) {

            // @todo should we check this on sendMSG instead?
        }

        if (frame.getMessageType() == Message.MESSAGE_TYPE_NUL) {
            synchronized (recvReplyQueue) {
                if (recvReplyQueue.size() != 0) {

                    // There are ANS messages on the queue for which we
                    // haven't received the last frame.
                    log.debug("Received NUL before last ANS");
                    session.terminate("Received NUL before last ANS");
                }
            }

            m = new MessageImpl(this, frame.getMsgno(), null, Message.MESSAGE_TYPE_NUL);

            mstatus.setMessageStatus(MessageStatus.MESSAGE_STATUS_RECEIVED_REPLY);
            if (log.isDebugEnabled()) {
                log.debug("Notifying reply listener for channel " + this.getNumber() + " => " + replyListener
                        + " for NUL message");
            }

            replyListener.receiveNUL(m);

            return;
        }

        synchronized (recvReplyQueue) {
            // is this an ANS message?
            if (frame.getMessageType() == Message.MESSAGE_TYPE_ANS) {

                // see if this answer number has already come in
                Iterator i = recvReplyQueue.iterator();

                m = null;

                while (i.hasNext()) {
                    MessageImpl tmp = (MessageImpl) i.next();

                    if (tmp.getAnsno() == frame.getAnsno()) {
                        m = tmp;

                        break;
                    }
                }

                // if no answer was found, then create a new one and
                // add it to the queue
                if (m == null) {
                    m = new MessageImpl(this, frame.getMsgno(), frame.getAnsno(), new InputDataStream(this));

                    if (!frame.isLast()) {
                        recvReplyQueue.add(m);
                    }
                } else if (frame.isLast()) {

                    // remove the found ANS from the recvReplyQueue
                    i.remove();
                }
            } else { // ERR or RPY
                if (recvReplyQueue.size() == 0) {
                    m = new MessageImpl(this, frame.getMsgno(), new InputDataStream(this), frame.getMessageType());

                    if (frame.isLast() == false) {
                        recvReplyQueue.add(m);
                    }
                } else {
                    // @todo sanity check: make sure this is the
                    // right Message
                    m = (MessageImpl) recvReplyQueue.getFirst();

                    if (frame.isLast()) {
                        recvReplyQueue.removeFirst();
                    }
                }

                if (frame.isLast()) {
                    if (frame.getMessageType() == Message.MESSAGE_TYPE_ERR) {
                        mstatus.setMessageStatus(MessageStatus.MESSAGE_STATUS_RECEIVED_ERROR);
                    } else {
                        mstatus.setMessageStatus(MessageStatus.MESSAGE_STATUS_RECEIVED_REPLY);
                    }
                }
            }

            Iterator i = frame.getPayload();
            while (i.hasNext()) {
                m.getDataStream().add((BufferSegment) i.next());
            }

            if (frame.isLast()) {
                m.getDataStream().setComplete();
            }

            // notify ANS message listener if this message has not been notified before
            if (frame.getMessageType() == Message.MESSAGE_TYPE_ANS) {
                synchronized (m) {
                    if (m.isNotified()) {
                        return;
                    }
                    m.setNotified();
                }
            }

            if (log.isDebugEnabled()) {
                log.debug("Notifying reply listener for channel " + this.getNumber() + ", msgno " + m.getMsgno()
                        + ", isLast " + m.getDataStream().isComplete() + " => " + replyListener);
            }

        } // end sync

        if (m.messageType == Message.MESSAGE_TYPE_RPY) {
            replyListener.receiveRPY(m);
        } else if (m.messageType == Message.MESSAGE_TYPE_ERR) {
            replyListener.receiveERR(m);
        } else if (m.messageType == Message.MESSAGE_TYPE_ANS) {
            replyListener.receiveANS(m);
        }
    }

    /**
     * interface between the session.  The session receives a frame and then
     * calls this function.  The function then calls the message listener
     * via some intermediary thread functions.  The message hasn't been
     * completely received.  The data stream contained in the message will
     * block if more is expected.
     * @param frame - the frame received by the session
     */
    boolean postFrame(Frame frame) throws BEEPException {
        log.trace("Channel::postFrame");

        if (state != STATE_ACTIVE && state != STATE_TUNING) {
            throw new BEEPException("State is " + state);
        }

        validateFrame(frame);

        synchronized (session) {

            recvSequence += frame.getSize();
            if (recvSequence > frame.MAX_SEQUENCE_NUMBER)
                recvSequence = (recvSequence - 1) % frame.MAX_SEQUENCE_NUMBER;

            //log.debug("recvWindowUsed was " + recvWindowUsed + " and we are about to add " + frame.getSize() + " to it.");

            // subtract this from the amount available in the buffer
            recvWindowUsed.getAndAdd(frame.getSize());

            // make sure we didn't overflow the buffer
            if (recvWindowUsed.intValue() > recvWindowSize.intValue()) {
                throw new BEEPException("Channel window overflow");
            }

        }

        receiveFrame(frame);

        if (frame.getMessageType() == Message.MESSAGE_TYPE_MSG) {
            return !(frame.isLast() == true && tuningProfile == true);
        } else {
            return !(frame.isLast() == true && getState() == STATE_TUNING);
        }
    }

    void sendMessage(MessageStatus m) throws BEEPException {
        if (state != STATE_ACTIVE && state != STATE_TUNING) {
            switch (state) {
            case STATE_INITIALIZED:
                throw new BEEPException("Channel is uninitialised.");
            default:
                throw new BEEPException("Channel is in an unknown state.");
            }
        }

        // send it on the session
        sendToPeer(m);
    }

    private void sendToPeer(MessageStatus status) throws BEEPException {
        synchronized (pendingSendMessages) {
            pendingSendMessages.add(status);
        }
        status.getMessageData().setChannel(this);
        sendQueuedMessages();
    }

    synchronized void sendQueuedMessages() throws BEEPException {
        synchronized (this) {
            while (true) {
                MessageStatus status;

                synchronized (pendingSendMessages) {
                    if (pendingSendMessages.isEmpty()) {
                        return;
                    }
                    status = (MessageStatus) pendingSendMessages.removeFirst();
                }

                if (this.recvWindowFreed.intValue() != 0) {
                    sendWindowUpdate();
                }

                sendFrames(status);

                if (status.getMessageStatus() != MessageStatus.MESSAGE_STATUS_SENT) {
                    synchronized (pendingSendMessages) {
                        pendingSendMessages.addFirst(status);
                    }
                    return;
                }
            }
        }
    }

    private void sendFrames(MessageStatus status) throws BEEPException {
        int sessionBufferSize = session.getMaxFrameSize();
        OutputDataStream ds = status.getMessageData();

        do {
            synchronized (this) {
                Frame frame;
                // create a frame
                frame = new Frame(status.getMessageType(), this, status.getMsgno(), false, sentSequence, 0,
                        status.getAnsno());

                // make sure the other peer can accept something
                if (peerWindowSize.intValue() == 0) {
                    return;
                }

                int maxToSend = Math.min(sessionBufferSize, peerWindowSize.intValue());

                //log.debug("Calculated maxToSend = " + maxToSend);

                int size = 0;
                while (size < maxToSend) {
                    if (ds.availableSegment() == false) {
                        if (size == 0) {
                            if (ds.isComplete() == false) {
                                // More BufferSegments are expected...
                                return;
                            }

                            frame.addPayload(zeroLengthSegment);
                        }

                        // Send what we have
                        break;
                    }

                    BufferSegment b = ds.getNextSegment(maxToSend - size);

                    frame.addPayload(b);

                    size += b.getLength();
                }
                //log.debug("Frame: " + sentSequence + " Actually sending: " + size);

                if (ds.isComplete() && ds.availableSegment() == false) {
                    frame.setLast();
                }

                try {
                    session.sendFrame(frame);
                } catch (BEEPException e) {
                    /*
                     * @todo we should do something more than just log
                     * the error (e.g. close the channel or session).
                     */
                    log.error("sendFrames", e);
                    status.setMessageStatus(MessageStatus.MESSAGE_STATUS_NOT_SENT);

                    throw e;
                }

                // update the sequence and peer window size
                //sentSequence += size;
                if ((sentSequence + size) > Frame.MAX_SEQUENCE_NUMBER) {
                    sentSequence = (sentSequence + size - 1) % Frame.MAX_SEQUENCE_NUMBER;
                } else {
                    sentSequence = sentSequence + size;
                }
                peerWindowSize.getAndAdd(-1 * size);
            }
        } while (ds.availableSegment() == true || ds.isComplete() == false);

        status.setMessageStatus(MessageStatus.MESSAGE_STATUS_SENT);

        if (ds.isComplete() && ds.availableSegment() == false
                && (status.getMessageType() == Message.MESSAGE_TYPE_RPY
                        || status.getMessageType() == Message.MESSAGE_TYPE_ERR
                        || status.getMessageType() == Message.MESSAGE_TYPE_NUL)) {
            removeFirstFromMSGQueue();
        }
    }

    public void removeFirstPiggyback() throws BEEPException {
        synchronized (recvMSGQueue) {
            MessageMSG m = (MessageMSGImpl) recvMSGQueue.getFirst();
            if (m instanceof PiggybackedMSG) {
                removeFirstFromMSGQueue();
            }
        }
    }

    private void removeFirstFromMSGQueue() throws BEEPException {
        MessageMSGImpl m;
        synchronized (recvMSGQueue) {
            Object removeFirst = recvMSGQueue.removeFirst();

            if (recvMSGQueue.size() != 0) {
                m = (MessageMSGImpl) recvMSGQueue.getFirst();
                synchronized (m) {
                    m.setNotified();
                }
            } else {
                m = null;
            }
        }

        if (m != null) {
            try {
                callbackQueue.execute(this);
            } catch (InterruptedException e) {
                /** @TODO handle this better */
                throw new BEEPException(e);
            }
        }
    }

    private void sendWindowUpdate() throws BEEPException {
        synchronized (session) {
            if (session.updateMyReceiveBufferSize(this, recvSequence,
                    recvWindowSize.intValue() - (recvWindowUsed.intValue() - recvWindowFreed.intValue()))) {
                recvWindowUsed.getAndAdd(-1 * recvWindowFreed.intValue());
                recvWindowFreed.set(0);
            }
        }
    }

    /**
     * Method setState
     *
     *
     * @param newState
     *
     * @throws BEEPException
     *
     */
    synchronized void setState(int newState) {
        log.trace("CH" + number + " state=" + newState);

        this.state = newState;

        /**
         * @todo state transition rules and error checking
         */
        if (false) {
            session.terminate("Bad state transition in channel");
        }
    }

    void setProfile(String profile) {
        this.profile = profile;
    }

    /**
     * Returns the profile for this channel.
     */
    public String getProfile() {
        return this.profile;
    }

    synchronized void updatePeerReceiveBufferSize(long lastSeq, int size) {
        synchronized (this) {
            int previousPeerWindowSize = peerWindowSize.intValue();

            // Handle case where sentSequence wraps around Frame.MAX_SEQUENCE_NUMBER
            if (sentSequence >= lastSeq) {
                peerWindowSize.set(size - (int) (sentSequence - lastSeq));
            } else {
                peerWindowSize.set(size - (int) (Frame.MAX_SEQUENCE_NUMBER + sentSequence - lastSeq + 1));
            }

            if (log.isDebugEnabled()) {
                log.debug("updatePeerReceiveBufferSize: channel " + this.getNumber() + ", size " + size
                        + ", lastSeq " + lastSeq + ", sentSequence " + sentSequence + ", previousPeerWindowSize "
                        + previousPeerWindowSize + ", peerWindowSize " + peerWindowSize);
            }

            if ((previousPeerWindowSize == 0) && (peerWindowSize.intValue() > 0)) {
                try {
                    sendQueuedMessages();
                } catch (BEEPException e) {
                }
            }
        }
    }

    private void validateFrame(Frame frame) throws BEEPException {
        synchronized (this) {

            if (previousFrame == null) {
                // is the message number correct?
                if (frame.getMessageType() == Message.MESSAGE_TYPE_MSG) {
                    synchronized (recvMSGQueue) {
                        ListIterator i = recvMSGQueue.listIterator(recvMSGQueue.size());
                        while (i.hasPrevious()) {
                            if (((Message) i.previous()).getMsgno() == frame.getMsgno()) {
                                throw new BEEPException("Received a frame " + "with a duplicate " + "msgno ("
                                        + frame.getMsgno() + ")");
                            }
                        }
                    }
                } else {
                    MessageStatus mstatus;

                    synchronized (sentMSGQueue) {
                        if (sentMSGQueue.size() == 0) {
                            throw new BEEPException("Received unsolicited reply");
                        }

                        mstatus = (MessageStatus) sentMSGQueue.get(0);
                    }

                    if (frame.getMsgno() != mstatus.getMsgno()) {
                        throw new BEEPException("Incorrect message number: was " + frame.getMsgno() + "; expecting "
                                + mstatus.getMsgno());
                    }
                }
            } else {
                // is the message type the same as the previous frames?
                if (previousFrame.getMessageType() != frame.getMessageType()) {
                    throw new BEEPException("Incorrect message type: was " + frame.getMessageTypeString()
                            + "; expecting " + previousFrame.getMessageTypeString());
                }

                // is the message number correct?
                if (frame.getMessageType() == Message.MESSAGE_TYPE_MSG
                        && frame.getMsgno() != previousFrame.getMsgno()) {
                    throw new BEEPException("Incorrect message number: was " + frame.getMsgno() + "; expecting "
                            + previousFrame.getMsgno());
                }
            }

            // is the sequence number correct?
            if (frame.getSeqno() != recvSequence) {
                throw new BEEPException(
                        "Incorrect sequence number: was " + frame.getSeqno() + "; expecting " + recvSequence);
            }

        }

        if (frame.getMessageType() != Message.MESSAGE_TYPE_MSG) {
            MessageStatus mstatus;

            synchronized (sentMSGQueue) {
                if (sentMSGQueue.size() == 0) {
                    throw new BEEPException("Received unsolicited reply");
                }

                mstatus = (MessageStatus) sentMSGQueue.get(0);

                if (mstatus.getMsgno() != frame.getMsgno()) {
                    throw new BEEPException("Received reply out of order");
                }
            }
        }

        // save the previous frame to compare message types
        if (frame.isLast()) {
            previousFrame = null;
        } else {
            previousFrame = frame;
        }

    }

    synchronized void freeReceiveBufferBytes(int size) {
        synchronized (this) {
            if (log.isTraceEnabled()) {
                log.trace("Freed up " + size + " bytes on channel " + number);
            }

            recvWindowFreed.getAndAdd(size);

            if (log.isTraceEnabled()) {
                log.trace("recvWindowUsed = " + recvWindowUsed + " recvWindowFreed = " + recvWindowFreed
                        + " recvWindowSize = " + recvWindowSize);
            }

            if (state == ChannelImpl.STATE_ACTIVE && recvWindowFreed.intValue() >= recvWindowSize.intValue() / 2) {
                try {
                    sendWindowUpdate();
                } catch (BEEPException e) {

                    // do nothing
                    log.fatal("Error updating receive buffer size", e);
                }
            }
        }
    }

    /**
     * Method getAvailableWindow
     *
     *
     * @return int the amount of free buffer space
     * available.
     *
     * This is called from Session to provide a # used
     * to screen frame sizes against and enforce the
     * protocol.
     *
     */
    synchronized int getAvailableWindow() {
        return (recvWindowSize.intValue() - recvWindowUsed.intValue());
    }

    /**
     * Used to set data that can be piggybacked on
     * a profile reply to a start channel request
     * (or any other scenario we choose)
     *
     * called by Channel Zero
     */
    public void setStartData(String data) {
        startData = data;
    }

    /**
     * Used to get data that can be piggybacked on
     * a profile reply to a start channel request
     * (or any other scenario we choose)
     *
     * Could be called by users, profile implementors etc.
     * to fetch data off a profile response.
     *
     * @return String the attached data, if any
     */
    public String getStartData() {
        return startData;
    }

    private static class DefaultMSGHandler implements RequestHandler {
        public void receiveMSG(MessageMSG message) {
            log.error("No handler registered to process MSG received on " + "channel "
                    + message.getChannel().getNumber());
            try {
                message.sendERR(BEEPError.CODE_REQUESTED_ACTION_ABORTED, "No MSG handler registered");
            } catch (BEEPException e) {
                log.error("Error sending ERR", e);
            }
        }

        private Log log = LogFactory.getLog(this.getClass());
    }
}