aboutsummaryrefslogtreecommitdiffstats
path: root/sshlib/src/main/java/com/trilead/ssh2/channel/Channel.java
blob: 8365f12838808eceac169b1b61b84d040f438d0b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
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;
		}
	}
}