/*
 * Copyright (c) 2006-2011 Christian Plattner. All rights reserved.
 * Please refer to the LICENSE.txt for licensing details.
 */
package ch.ethz.ssh2.channel;

/**
 * Channel.
 *
 * @author Christian Plattner
 * @version $Id$
 */
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.
		  *
		  */

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

	static final int CHANNEL_BUFFER_SIZE = 32 * 1024 * 3 * 2;

	/*
		  * 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;

	// In case this channel belongs to a server-side session.
	ServerSessionImpl ss;

	// 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 = 32 * 1024;

		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;
			}
		}
	}

	public int getState()
	{
		return this.state;
	}
}
