
package com.trilead.ssh2.channel;

/**
 * Channel.
 * 
 * @author Christian Plattner, plattner@trilead.com
 * @version $Id: Channel.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
 */
public class Channel
{
	/*
	 * OK. Here is an important part of the JVM Specification:
	 * (http://java.sun.com/docs/books/vmspec/2nd-edition/html/Threads.doc.html#22214)
	 * 
	 * Any association between locks and variables is purely conventional.
	 * Locking any lock conceptually flushes all variables from a thread's
	 * working memory, and unlocking any lock forces the writing out to main
	 * memory of all variables that the thread has assigned. That a lock may be
	 * associated with a particular object or a class is purely a convention.
	 * (...)
	 * 
	 * If a thread uses a particular shared variable only after locking a
	 * particular lock and before the corresponding unlocking of that same lock,
	 * then the thread will read the shared value of that variable from main
	 * memory after the lock operation, if necessary, and will copy back to main
	 * memory the value most recently assigned to that variable before the
	 * unlock operation.
	 * 
	 * This, in conjunction with the mutual exclusion rules for locks, suffices
	 * to guarantee that values are correctly transmitted from one thread to
	 * another through shared variables.
	 * 
	 * ====> Always keep that in mind when modifying the Channel/ChannelManger
	 * code.
	 * 
	 */

	static final int STATE_OPENING = 1;
	static final int STATE_OPEN = 2;
	static final int STATE_CLOSED = 4;

	static final int CHANNEL_BUFFER_SIZE = 30000;

	/*
	 * To achieve correctness, the following rules have to be respected when
	 * accessing this object:
	 */

	// These fields can always be read
	final ChannelManager cm;
	final ChannelOutputStream stdinStream;
	final ChannelInputStream stdoutStream;
	final ChannelInputStream stderrStream;

	// These two fields will only be written while the Channel is in state
	// STATE_OPENING.
	// The code makes sure that the two fields are written out when the state is
	// changing to STATE_OPEN.
	// Therefore, if you know that the Channel is in state STATE_OPEN, then you
	// can read these two fields without synchronizing on the Channel. However, make
	// sure that you get the latest values (e.g., flush caches by synchronizing on any
	// object). However, to be on the safe side, you can lock the channel.

	int localID = -1;
	int remoteID = -1;

	/*
	 * Make sure that we never send a data/EOF/WindowChange msg after a CLOSE
	 * msg.
	 * 
	 * This is a little bit complicated, but we have to do it in that way, since
	 * we cannot keep a lock on the Channel during the send operation (this
	 * would block sometimes the receiver thread, and, in extreme cases, can
	 * lead to a deadlock on both sides of the connection (senders are blocked
	 * since the receive buffers on the other side are full, and receiver
	 * threads wait for the senders to finish). It all depends on the
	 * implementation on the other side. But we cannot make any assumptions, we
	 * have to assume the worst case. Confused? Just believe me.
	 */

	/*
	 * If you send a message on a channel, then you have to aquire the
	 * "channelSendLock" and check the "closeMessageSent" flag (this variable
	 * may only be accessed while holding the "channelSendLock" !!!
	 * 
	 * BTW: NEVER EVER SEND MESSAGES FROM THE RECEIVE THREAD - see explanation
	 * above.
	 */

	final Object channelSendLock = new Object();
	boolean closeMessageSent = false;

	/*
	 * Stop memory fragmentation by allocating this often used buffer.
	 * May only be used while holding the channelSendLock
	 */

	final byte[] msgWindowAdjust = new byte[9];

	// If you access (read or write) any of the following fields, then you have
	// to synchronize on the channel.

	int state = STATE_OPENING;

	boolean closeMessageRecv = false;

	/* This is a stupid implementation. At the moment we can only wait
	 * for one pending request per channel.
	 */
	int successCounter = 0;
	int failedCounter = 0;

	int localWindow = 0; /* locally, we use a small window, < 2^31 */
	long remoteWindow = 0; /* long for readable  2^32 - 1 window support */

	int localMaxPacketSize = -1;
	int remoteMaxPacketSize = -1;

	final byte[] stdoutBuffer = new byte[CHANNEL_BUFFER_SIZE];
	final byte[] stderrBuffer = new byte[CHANNEL_BUFFER_SIZE];

	int stdoutReadpos = 0;
	int stdoutWritepos = 0;
	int stderrReadpos = 0;
	int stderrWritepos = 0;

	boolean EOF = false;

	Integer exit_status;

	String exit_signal;

	// we keep the x11 cookie so that this channel can be closed when this
	// specific x11 forwarding gets stopped

	String hexX11FakeCookie;

	// reasonClosed is special, since we sometimes need to access it
	// while holding the channelSendLock.
	// We protect it with a private short term lock.

	private final Object reasonClosedLock = new Object();
	private String reasonClosed = null;

	public Channel(ChannelManager cm)
	{
		this.cm = cm;

		this.localWindow = CHANNEL_BUFFER_SIZE;
		this.localMaxPacketSize = 35000 - 1024; // leave enough slack

		this.stdinStream = new ChannelOutputStream(this);
		this.stdoutStream = new ChannelInputStream(this, false);
		this.stderrStream = new ChannelInputStream(this, true);
	}

	/* Methods to allow access from classes outside of this package */

	public ChannelInputStream getStderrStream()
	{
		return stderrStream;
	}

	public ChannelOutputStream getStdinStream()
	{
		return stdinStream;
	}

	public ChannelInputStream getStdoutStream()
	{
		return stdoutStream;
	}

	public String getExitSignal()
	{
		synchronized (this)
		{
			return exit_signal;
		}
	}

	public Integer getExitStatus()
	{
		synchronized (this)
		{
			return exit_status;
		}
	}

	public String getReasonClosed()
	{
		synchronized (reasonClosedLock)
		{
			return reasonClosed;
		}
	}

	public void setReasonClosed(String reasonClosed)
	{
		synchronized (reasonClosedLock)
		{
			if (this.reasonClosed == null)
				this.reasonClosed = reasonClosed;
		}
	}
}
