view app/src/main/java/ch/ethz/ssh2/channel/Channel.java @ 467:bf8c2af0f106

Added tag stable-1.9.3-6 for changeset 12e2d9dd95df
author Carl Byington <carl@five-ten-sg.com>
date Thu, 05 Sep 2019 09:25:55 -0700
parents d29cce60f393
children
line wrap: on
line source

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

import java.io.IOException;

import ch.ethz.ssh2.transport.TransportManager;

/**
 * Channel.
 *
 * @author Christian Plattner
 * @version $Id: Channel.java 123 2014-04-12 21:11:47Z dkocher@sudo.ch $
 */
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 IOException reasonClosed = null;

    public Channel(ChannelManager cm) {
        this.cm = cm;
        this.localWindow = CHANNEL_BUFFER_SIZE;
        this.localMaxPacketSize = TransportManager.MAX_PACKET_SIZE;
        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 IOException getReasonClosed() {
        synchronized (reasonClosedLock) {
            return reasonClosed;
        }
    }

    public void setReasonClosed(IOException e) {
        synchronized (reasonClosedLock) {
            this.reasonClosed = e;
        }
    }

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