001: /*
002: * SSHTools - Java SSH2 API
003: *
004: * Copyright (C) 2002-2003 Lee David Painter and Contributors.
005: *
006: * Contributions made by:
007: *
008: * Brett Smith
009: * Richard Pernavas
010: * Erwin Bolwidt
011: *
012: * This program is free software; you can redistribute it and/or
013: * modify it under the terms of the GNU General Public License
014: * as published by the Free Software Foundation; either version 2
015: * of the License, or (at your option) any later version.
016: *
017: * This program is distributed in the hope that it will be useful,
018: * but WITHOUT ANY WARRANTY; without even the implied warranty of
019: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
020: * GNU General Public License for more details.
021: *
022: * You should have received a copy of the GNU General Public License
023: * along with this program; if not, write to the Free Software
024: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
025: */
026: package com.sshtools.j2ssh.connection;
027:
028: import org.apache.commons.logging.Log;
029: import org.apache.commons.logging.LogFactory;
030:
031: import java.io.IOException;
032:
033: import java.util.Iterator;
034: import java.util.Vector;
035:
036: /**
037: *
038: *
039: * @author $author$
040: * @version $Revision: 1.74 $
041: */
042: public abstract class Channel {
043: private static Log log = LogFactory.getLog(Channel.class);
044:
045: /** */
046: protected ChannelDataWindow localWindow = new ChannelDataWindow();
047:
048: /** */
049: protected ChannelDataWindow remoteWindow = new ChannelDataWindow();
050:
051: /** */
052: protected ConnectionProtocol connection;
053:
054: /** */
055: protected long localChannelId;
056:
057: /** */
058: protected long localPacketSize;
059:
060: /** */
061: protected long remoteChannelId;
062:
063: /** */
064: protected long remotePacketSize;
065:
066: /** */
067: protected ChannelState state = new ChannelState();
068: private boolean isClosed = false;
069: private boolean isLocalEOF = false;
070: private boolean isRemoteEOF = false;
071: private boolean localHasClosed = false;
072: private boolean remoteHasClosed = false;
073: private String name = "Unnamed Channel";
074: private Vector eventListeners = new Vector();
075:
076: /**
077: * Creates a new Channel object.
078: */
079: public Channel() {
080: this .localPacketSize = getMaximumPacketSize();
081: this .localWindow.increaseWindowSpace(getMaximumWindowSpace());
082: }
083:
084: /**
085: *
086: *
087: * @return
088: */
089: public abstract byte[] getChannelOpenData();
090:
091: /**
092: *
093: *
094: * @return
095: */
096: public abstract byte[] getChannelConfirmationData();
097:
098: /**
099: *
100: *
101: * @return
102: */
103: public abstract String getChannelType();
104:
105: /**
106: *
107: *
108: * @return
109: */
110: protected abstract int getMinimumWindowSpace();
111:
112: /**
113: *
114: *
115: * @return
116: */
117: protected abstract int getMaximumWindowSpace();
118:
119: /**
120: *
121: *
122: * @return
123: */
124: protected abstract int getMaximumPacketSize();
125:
126: /**
127: *
128: *
129: * @param msg
130: *
131: * @throws IOException
132: */
133: protected abstract void onChannelData(SshMsgChannelData msg)
134: throws IOException;
135:
136: /**
137: *
138: *
139: * @param msg
140: *
141: * @throws IOException
142: */
143: protected void processChannelData(SshMsgChannelData msg)
144: throws IOException {
145: synchronized (state) {
146: if (!isClosed()) {
147: if (msg.getChannelDataLength() > localWindow
148: .getWindowSpace()) {
149: throw new IOException(
150: "More data recieved than is allowed by the channel data window ["
151: + name + "]");
152: }
153:
154: long windowSpace = localWindow.consumeWindowSpace(msg
155: .getChannelData().length);
156:
157: if (windowSpace < getMinimumWindowSpace()) {
158: if (log.isDebugEnabled()) {
159: log.debug("Channel "
160: + String.valueOf(localChannelId)
161: + " requires more window space ["
162: + name + "]");
163: }
164:
165: windowSpace = getMaximumWindowSpace() - windowSpace;
166: log
167: .debug("Requesting connection protocol increase window");
168: connection.sendChannelWindowAdjust(this ,
169: windowSpace);
170: localWindow.increaseWindowSpace(windowSpace);
171: }
172:
173: onChannelData(msg);
174:
175: Iterator it = eventListeners.iterator();
176: ChannelEventListener eventListener;
177:
178: while (it.hasNext()) {
179: eventListener = (ChannelEventListener) it.next();
180:
181: if (eventListener != null) {
182: eventListener.onDataReceived(this , msg
183: .getChannelData());
184: }
185: }
186: } else {
187: throw new IOException(
188: "Channel data received but channel is closed ["
189: + name + "]");
190: }
191: }
192: }
193:
194: /**
195: *
196: *
197: * @return
198: */
199: public boolean isClosed() {
200: synchronized (state) {
201: return state.getValue() == ChannelState.CHANNEL_CLOSED;
202: }
203: }
204:
205: /**
206: *
207: *
208: * @return
209: */
210: public boolean isOpen() {
211: synchronized (state) {
212: return state.getValue() == ChannelState.CHANNEL_OPEN;
213: }
214: }
215:
216: /**
217: *
218: *
219: * @param data
220: *
221: * @throws IOException
222: */
223: protected/*synchronized*/void sendChannelData(byte[] data)
224: throws IOException {
225: if (!connection.isConnected()) {
226: throw new IOException("The connection has been closed ["
227: + name + "]");
228: }
229:
230: if (!isClosed()) {
231: connection.sendChannelData(this , data);
232:
233: Iterator it = eventListeners.iterator();
234: ChannelEventListener eventListener;
235:
236: while (it.hasNext()) {
237: eventListener = (ChannelEventListener) it.next();
238:
239: if (eventListener != null) {
240: eventListener.onDataSent(this , data);
241: }
242: }
243: } else {
244: throw new IOException("The channel is closed [" + name
245: + "]");
246: }
247: }
248:
249: /**
250: *
251: *
252: * @param type
253: * @param data
254: *
255: * @throws IOException
256: */
257: protected/*synchronized*/void sendChannelExtData(int type,
258: byte[] data) throws IOException {
259: if (!connection.isConnected()) {
260: throw new IOException("The connection has been closed ["
261: + name + "]");
262: }
263:
264: if (!isClosed()) {
265: connection.sendChannelExtData(this , type, data);
266:
267: Iterator it = eventListeners.iterator();
268: ChannelEventListener eventListener;
269:
270: while (it.hasNext()) {
271: eventListener = (ChannelEventListener) it.next();
272:
273: if (eventListener != null) {
274: eventListener.onDataSent(this , data);
275: }
276: }
277: } else {
278: throw new IOException("The channel is closed [" + name
279: + "]");
280: }
281: }
282:
283: /**
284: *
285: *
286: * @param msg
287: *
288: * @throws IOException
289: */
290: protected abstract void onChannelExtData(
291: SshMsgChannelExtendedData msg) throws IOException;
292:
293: /**
294: *
295: *
296: * @param msg
297: *
298: * @throws IOException
299: */
300: protected void processChannelData(SshMsgChannelExtendedData msg)
301: throws IOException {
302: synchronized (state) {
303: if (msg.getChannelData().length > localWindow
304: .getWindowSpace()) {
305: throw new IOException(
306: "More data recieved than is allowed by the channel data window ["
307: + name + "]");
308: }
309:
310: long windowSpace = localWindow.consumeWindowSpace(msg
311: .getChannelData().length);
312:
313: if (windowSpace < getMinimumWindowSpace()) {
314: if (log.isDebugEnabled()) {
315: log.debug("Channel "
316: + String.valueOf(localChannelId)
317: + " requires more window space [" + name
318: + "]");
319: }
320:
321: windowSpace = getMaximumWindowSpace() - windowSpace;
322: connection.sendChannelWindowAdjust(this , windowSpace);
323: localWindow.increaseWindowSpace(windowSpace);
324: }
325:
326: onChannelExtData(msg);
327:
328: Iterator it = eventListeners.iterator();
329: ChannelEventListener eventListener;
330:
331: while (it.hasNext()) {
332: eventListener = (ChannelEventListener) it.next();
333:
334: if (eventListener != null) {
335: eventListener.onDataReceived(this , msg
336: .getChannelData());
337: }
338: }
339: }
340: }
341:
342: /**
343: *
344: *
345: * @return
346: */
347: public long getLocalChannelId() {
348: return localChannelId;
349: }
350:
351: /**
352: *
353: *
354: * @return
355: */
356: public long getLocalPacketSize() {
357: return localPacketSize;
358: }
359:
360: /**
361: *
362: *
363: * @return
364: */
365: public ChannelDataWindow getLocalWindow() {
366: return localWindow;
367: }
368:
369: /**
370: *
371: *
372: * @return
373: */
374: public long getRemoteChannelId() {
375: return remoteChannelId;
376: }
377:
378: /**
379: *
380: *
381: * @return
382: */
383: public long getRemotePacketSize() {
384: return remotePacketSize;
385: }
386:
387: /**
388: *
389: *
390: * @return
391: */
392: public ChannelDataWindow getRemoteWindow() {
393: return remoteWindow;
394: }
395:
396: /**
397: *
398: *
399: * @return
400: */
401: public ChannelState getState() {
402: return state;
403: }
404:
405: /**
406: *
407: *
408: * @throws IOException
409: */
410: public void close() throws IOException {
411: synchronized (state) {
412: if (isOpen()) {
413: if ((connection != null) && !localHasClosed
414: && connection.isConnected()) {
415: connection.closeChannel(this );
416: }
417:
418: localHasClosed = true;
419:
420: if (log.isDebugEnabled()) {
421: log
422: .debug("Connection is "
423: + ((connection == null) ? "null"
424: : (connection.isConnected() ? "connected"
425: : "not connected")));
426: }
427:
428: if (remoteHasClosed
429: || ((connection == null) || !connection
430: .isConnected())) {
431: log.info("Finializing channel close");
432: finalizeClose();
433: }
434: }
435: }
436: }
437:
438: /**
439: *
440: *
441: * @throws IOException
442: */
443: protected void remoteClose() throws IOException {
444: log.info("Remote side is closing channel");
445:
446: synchronized (state) {
447: remoteHasClosed = true;
448: close();
449: }
450: }
451:
452: /**
453: *
454: *
455: * @throws IOException
456: */
457: protected void finalizeClose() throws IOException {
458: synchronized (state) {
459: state.setValue(ChannelState.CHANNEL_CLOSED);
460: onChannelClose();
461:
462: Iterator it = eventListeners.iterator();
463: ChannelEventListener eventListener;
464:
465: while (it.hasNext()) {
466: eventListener = (ChannelEventListener) it.next();
467:
468: if (eventListener != null) {
469: eventListener.onChannelClose(this );
470: }
471: }
472:
473: if (connection != null) {
474: connection.freeChannel(this );
475: }
476: }
477: }
478:
479: /**
480: *
481: *
482: * @throws IOException
483: */
484: public void setLocalEOF() throws IOException {
485: synchronized (state) {
486: isLocalEOF = true;
487: connection.sendChannelEOF(this );
488: }
489: }
490:
491: /**
492: *
493: *
494: * @return
495: */
496: public boolean isLocalEOF() {
497: return isLocalEOF;
498: }
499:
500: /**
501: *
502: *
503: * @return
504: */
505: public boolean isRemoteEOF() {
506: return isRemoteEOF;
507: }
508:
509: /**
510: *
511: *
512: * @throws IOException
513: */
514: protected void setRemoteEOF() throws IOException {
515: synchronized (state) {
516: isRemoteEOF = true;
517: onChannelEOF();
518:
519: Iterator it = eventListeners.iterator();
520: ChannelEventListener eventListener;
521:
522: while (it.hasNext()) {
523: eventListener = (ChannelEventListener) it.next();
524:
525: if (eventListener != null) {
526: eventListener.onChannelEOF(this );
527: }
528: }
529: }
530: }
531:
532: /**
533: *
534: *
535: * @param eventListener
536: */
537: public void addEventListener(ChannelEventListener eventListener) {
538: eventListeners.add(eventListener);
539: }
540:
541: /**
542: *
543: *
544: * @param connection
545: * @param localChannelId
546: * @param senderChannelId
547: * @param initialWindowSize
548: * @param maximumPacketSize
549: *
550: * @throws IOException
551: */
552: protected void init(ConnectionProtocol connection,
553: long localChannelId, long senderChannelId,
554: long initialWindowSize, long maximumPacketSize)
555: throws IOException {
556: this .localChannelId = localChannelId;
557: this .remoteChannelId = senderChannelId;
558: this .remotePacketSize = maximumPacketSize;
559: this .remoteWindow.increaseWindowSpace(initialWindowSize);
560: this .connection = connection;
561:
562: synchronized (state) {
563: state.setValue(ChannelState.CHANNEL_OPEN);
564: }
565: }
566:
567: /**
568: *
569: *
570: * @throws IOException
571: */
572: protected void open() throws IOException {
573: synchronized (state) {
574: state.setValue(ChannelState.CHANNEL_OPEN);
575: onChannelOpen();
576:
577: Iterator it = eventListeners.iterator();
578: ChannelEventListener eventListener;
579:
580: while (it.hasNext()) {
581: eventListener = (ChannelEventListener) it.next();
582:
583: if (eventListener != null) {
584: eventListener.onChannelOpen(this );
585: }
586: }
587: }
588: }
589:
590: /**
591: *
592: *
593: * @param connection
594: * @param localChannelId
595: * @param senderChannelId
596: * @param initialWindowSize
597: * @param maximumPacketSize
598: * @param eventListener
599: *
600: * @throws IOException
601: */
602: protected void init(ConnectionProtocol connection,
603: long localChannelId, long senderChannelId,
604: long initialWindowSize, long maximumPacketSize,
605: ChannelEventListener eventListener) throws IOException {
606: if (eventListener != null) {
607: addEventListener(eventListener);
608: }
609:
610: init(connection, localChannelId, senderChannelId,
611: initialWindowSize, maximumPacketSize);
612: }
613:
614: /**
615: *
616: *
617: * @throws IOException
618: */
619: protected abstract void onChannelClose() throws IOException;
620:
621: /**
622: *
623: *
624: * @throws IOException
625: */
626: protected abstract void onChannelEOF() throws IOException;
627:
628: /**
629: *
630: *
631: * @throws IOException
632: */
633: protected abstract void onChannelOpen() throws IOException;
634:
635: /**
636: *
637: *
638: * @param requestType
639: * @param wantReply
640: * @param requestData
641: *
642: * @throws IOException
643: */
644: protected abstract void onChannelRequest(String requestType,
645: boolean wantReply, byte[] requestData) throws IOException;
646:
647: /**
648: *
649: *
650: * @param name
651: */
652: public void setName(String name) {
653: this .name = name;
654: }
655:
656: /**
657: *
658: *
659: * @return
660: */
661: public String getName() {
662: return name;
663: }
664: }
|