All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sshtools.ssh2.Ssh2Channel Maven / Gradle / Ivy

The newest version!
/**
 * Copyright 2003-2016 SSHTOOLS Limited. All Rights Reserved.
 *
 * For product documentation visit https://www.sshtools.com/
 *
 * This file is part of J2SSH Maverick.
 *
 * J2SSH Maverick is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * J2SSH Maverick is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with J2SSH Maverick.  If not, see .
 */
package com.sshtools.ssh2;

import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.Vector;

import com.sshtools.logging.Log;
import com.sshtools.ssh.ChannelEventListener;
import com.sshtools.ssh.SshException;
import com.sshtools.ssh.SshIOException;
import com.sshtools.ssh.message.Message;
import com.sshtools.ssh.message.MessageObserver;
import com.sshtools.ssh.message.SshAbstractChannel;
import com.sshtools.ssh.message.SshChannelMessage;
import com.sshtools.ssh.message.SshMessage;
import com.sshtools.ssh.message.SshMessageStore;
import com.sshtools.util.ByteArrayReader;
import com.sshtools.util.ByteArrayWriter;

/**
 * 

* All terminal sessions, forwarded connections, etc are channels and this class * implements the base SSH2 channel. Either side may open a channel and multiple * channels are multiplexed into a single SSH connection. SSH2 channels are flow * controlled, no data may be sent to a channel until a message is received to * indicate that window space is available. *

* *

* To open a channel, first create an instance of the channel as follows: *

* *
 * Ssh2Channel channel = new Ssh2Channel("session", 32768, 32768);
 * 
* *
The second value passed into the constructor is the initial * window space and also the maximum amount of window space that will be * available to the other side. The channel manages the window space * automatically by increasing the amount available once the data has been read * from the channels InputStream and only when the current value falls below * half of the maximum. *

* *

* After the channel has been created the channel is opened through a call to openChannel(Ssh2Channel channel). Once the channel is open you can use * the IO streams to send and receive data. *

* * * @author Lee David Painter */ public class Ssh2Channel extends SshAbstractChannel { public static final String SESSION_CHANNEL = "session"; ConnectionProtocol connection; int remoteid; String name; Vector listeners = new Vector(); final static int SSH_MSG_CHANNEL_CLOSE = 97; final static int SSH_MSG_CHANNEL_EOF = 96; final static int SSH_MSG_CHANNEL_REQUEST = 98; final static int SSH_MSG_CHANNEL_SUCCESS = 99; final static int SSH_MSG_CHANNEL_FAILURE = 100; final static int SSH_MSG_WINDOW_ADJUST = 93; final static int SSH_MSG_CHANNEL_DATA = 94; final static int SSH_MSG_CHANNEL_EXTENDED_DATA = 95; boolean autoConsumeInput = false; boolean sendKeepAliveOnIdle = false; boolean isRemoteEOF = false; boolean isLocalEOF = false; final MessageObserver WINDOW_ADJUST_MESSAGES = new MessageObserver() { public boolean wantsNotification(Message msg) { switch (msg.getMessageId()) { case SSH_MSG_WINDOW_ADJUST: case SSH_MSG_CHANNEL_CLOSE: return true; default: return false; } } }; final MessageObserver CHANNEL_DATA_MESSAGES = new MessageObserver() { public boolean wantsNotification(Message msg) { // Access to this observer is synchronized by the ThreadSynchronizer // so we can flag our InputStream as blocking when the method is // called and released once we have found a message switch (msg.getMessageId()) { case SSH_MSG_CHANNEL_DATA: case SSH_MSG_CHANNEL_EOF: case SSH_MSG_CHANNEL_CLOSE: return true; default: return false; } } }; final MessageObserver EXTENDED_DATA_MESSAGES = new MessageObserver() { public boolean wantsNotification(Message msg) { switch (msg.getMessageId()) { case SSH_MSG_CHANNEL_EXTENDED_DATA: case SSH_MSG_CHANNEL_EOF: case SSH_MSG_CHANNEL_CLOSE: return true; default: return false; } } }; final MessageObserver CHANNEL_REQUEST_MESSAGES = new MessageObserver() { public boolean wantsNotification(Message msg) { switch (msg.getMessageId()) { case SSH_MSG_CHANNEL_SUCCESS: case SSH_MSG_CHANNEL_FAILURE: case SSH_MSG_CHANNEL_CLOSE: return true; default: return false; } } }; final MessageObserver CHANNEL_CLOSE_MESSAGES = new MessageObserver() { public boolean wantsNotification(Message msg) { switch (msg.getMessageId()) { case SSH_MSG_CHANNEL_CLOSE: return true; default: return false; } } }; final static MessageObserver STICKY_MESSAGES = new MessageObserver() { public boolean wantsNotification(Message msg) { switch (msg.getMessageId()) { case SSH_MSG_CHANNEL_CLOSE: case SSH_MSG_CHANNEL_EOF: return true; default: return false; } } }; ChannelInputStream in; ChannelOutputStream out; DataWindow localwindow; DataWindow remotewindow; boolean closing = false; boolean free = false; /** *

* Construct an SSH2 channel *

* * @param name * the name of the channel, for example "session" * @param windowsize * the initial window size * @param packetsize * the maximum packet size */ public Ssh2Channel(String name, int windowsize, int packetsize) { this.name = name; this.localwindow = new DataWindow(windowsize, packetsize); in = new ChannelInputStream(CHANNEL_DATA_MESSAGES); out = new ChannelOutputStream(); } protected MessageObserver getStickyMessageIds() { return STICKY_MESSAGES; } public void setAutoConsumeInput(boolean autoConsumeInput) { this.autoConsumeInput = autoConsumeInput; } long getWindowSize() { return localwindow.available(); } int getPacketSize() { return localwindow.getPacketSize(); } protected SshMessageStore getMessageStore() throws SshException { return super.getMessageStore(); } /** * Get the name of the channel. * * @return the name of the channel. */ public String getName() { return name; } public InputStream getInputStream() { return in; } public OutputStream getOutputStream() { return out; } public void addChannelEventListener(ChannelEventListener listener) { synchronized (listeners) { if (listener != null) { listeners.addElement(listener); } } } public boolean isSendKeepAliveOnIdle() { return sendKeepAliveOnIdle; } public void setSendKeepAliveOnIdle(boolean sendKeepAliveOnIdle) { this.sendKeepAliveOnIdle = sendKeepAliveOnIdle; } public void idle() { if (sendKeepAliveOnIdle) { try { sendRequest("[email protected]", false, null, false); } catch (SshException e) { } } } /** * Called to initialize the channels variables when creating/opening. * * @param connection * @param channelid */ void init(ConnectionProtocol connection, int channelid) { this.connection = connection; super.init(connection, channelid); } /** * Called after the channel has been created by the channelRequest() * . */ protected boolean processChannelMessage(SshChannelMessage msg) throws SshException { try { switch (msg.getMessageId()) { case SSH_MSG_CHANNEL_REQUEST: String requesttype = msg.readString(); boolean wantreply = msg.read() != 0; byte[] requestdata = new byte[msg.available()]; msg.read(requestdata); if (Log.isDebugEnabled()) { Log.debug(this, "Received SSH_MSG_CHANNEL_REQUEST id=" + channelid + " rid=" + remoteid + " request=" + requesttype + " wantreply=" + wantreply); } channelRequest(requesttype, wantreply, requestdata); return true; case SSH_MSG_WINDOW_ADJUST: if (Log.isDebugEnabled()) { msg.mark(4); int len = (int) msg.readInt(); Log.debug(this, "Received SSH_MSG_WINDOW_ADJUST id=" + channelid + " rid=" + remoteid + " window=" + remotewindow.available() + " adjust=" + len); msg.reset(); } return false; case SSH_MSG_CHANNEL_DATA: if (Log.isDebugEnabled()) { Log.debug( this, "Received SSH_MSG_CHANNEL_DATA id=" + channelid + " rid=" + remoteid + " len=" + (msg.available() - 4) + " window=" + localwindow.available()); } // Is the channels InputStream currently in a blocking read // operation? if it is then we can leave the message for it // to process (and subsequently break out of the block) or // if not we should process it here so that data events // are fired in a timely fashion if (autoConsumeInput) { localwindow.consume(msg.available() - 4); if (localwindow.available() <= localwindow.getInitialSize() / 2) { adjustWindow(localwindow.getInitialSize() - localwindow.available()); } } for (Enumeration e = listeners.elements(); e .hasMoreElements();) { (e.nextElement()).dataReceived(Ssh2Channel.this, msg.array(), msg.getPosition() + 4, msg.available() - 4); } return autoConsumeInput; case SSH_MSG_CHANNEL_EXTENDED_DATA: if (Log.isDebugEnabled()) { Log.debug(this, "Received SSH_MSG_CHANNEL_EXTENDED_DATA id=" + channelid + " rid=" + remoteid + " len=" + (msg.available() - 4) + " window=" + localwindow.available()); } int type = (int) ByteArrayReader.readInt(msg.array(), msg.getPosition()); if (autoConsumeInput) { localwindow.consume(msg.available() - 8); if (localwindow.available() <= localwindow.getInitialSize() / 2) { adjustWindow(localwindow.getInitialSize() - localwindow.available()); } } for (Enumeration e = listeners.elements(); e .hasMoreElements();) { (e.nextElement()).extendedDataReceived(Ssh2Channel.this, msg.array(), msg.getPosition() + 8, msg.available() - 8, type); } return autoConsumeInput; case SSH_MSG_CHANNEL_CLOSE: if (Log.isDebugEnabled()) { Log.debug(this, "Received SSH_MSG_CHANNEL_CLOSE id=" + channelid + " rid=" + remoteid); } // Synchronize on message store and close synchronized (this) { if (!closing) { synchronized (ms) { if (!ms.isClosed()) ms.close(); } } } checkCloseStatus(true); return false; case SSH_MSG_CHANNEL_EOF: if (Log.isDebugEnabled()) { Log.debug(this, "Received SSH_MSG_CHANNEL_EOF id=" + channelid + " rid=" + remoteid); } isRemoteEOF = true; for (Enumeration e = listeners.elements(); e .hasMoreElements();) { (e.nextElement()).channelEOF(Ssh2Channel.this); } // Fire the EOF event, do we need this on the listener? channelEOF(); if (isLocalEOF) { close(); } return false; default: return false; } } catch (IOException ex) { throw new SshException(ex, SshException.INTERNAL_ERROR); } } SshChannelMessage processMessages(MessageObserver messagefilter) throws SshException, EOFException { SshChannelMessage msg; /** * Collect the next channel message from the connection protocol */ msg = (SshChannelMessage) ms.nextMessage(messagefilter, 0); switch (msg.getMessageId()) { case SSH_MSG_WINDOW_ADJUST: try { remotewindow.adjust(msg.readInt()); if (Log.isDebugEnabled()) { Log.debug(this, "Applied window adjust window=" + remotewindow.available()); } } catch (IOException ex) { throw new SshException(SshException.INTERNAL_ERROR, ex); } break; case SSH_MSG_CHANNEL_DATA: try { int length = (int) msg.readInt(); processStandardData(length, msg); } catch (IOException e) { throw new SshException(SshException.INTERNAL_ERROR, e); } break; case SSH_MSG_CHANNEL_EXTENDED_DATA: try { int type = (int) msg.readInt(); int length = (int) msg.readInt(); processExtendedData(type, length, msg); } catch (IOException ex) { throw new SshException(SshException.INTERNAL_ERROR, ex); } break; /** * We dont remove these messages because other threads may be evaluating * the messages at the same time */ case SSH_MSG_CHANNEL_CLOSE: checkCloseStatus(true); throw new EOFException("The channel is closed"); case SSH_MSG_CHANNEL_EOF: throw new EOFException("The channel is EOF"); /** * Just return */ default: break; } return msg; } /** * Called when channel data arrives, by default this method makes the data * available in the channels InputStream. Override this method to change * this behaviour. * * @param buf * @param offset * @param len * @throws IOException */ protected void processStandardData(int length, SshChannelMessage msg) throws SshException { in.addMessage(length, msg); } protected void processStandardData(byte[] buf, int off, int len) throws SshException { in.addMessage(len, new SshChannelMessage(SSH_MSG_CHANNEL_DATA, buf, off, len)); } /** * Called when extended data arrives. This method fires the * {@link com.sshtools.ssh.ChannelEventListener#extendedDataReceived(com.sshtools.ssh.Channel, byte[], int, int, int)} * event so to maintain code compatibility always call the super * method in any overidden method. * * @param typecode * the type of extended data * @param buf * the data buffer * @param offset * the offset * @param len * the length * @throws SshException * @throws IOException */ protected void processExtendedData(int typecode, int length, SshChannelMessage msg) throws SshException { // Default implementation is to ignore extended data } /** * Currently reserved. * * @return ChannelInputStream */ protected ChannelInputStream createExtendedDataStream() { return new ChannelInputStream(EXTENDED_DATA_MESSAGES); } void sendChannelData(byte[] buf, int offset, int len) throws SshException { ByteArrayWriter msg = new ByteArrayWriter(len + 9); try { if (state != CHANNEL_OPEN) { throw new SshException("The channel is closed", SshException.CHANNEL_FAILURE); } if (len > 0) { msg.write(SSH_MSG_CHANNEL_DATA); msg.writeInt(remoteid); msg.writeBinaryString(buf, offset, len); if (Log.isDebugEnabled()) { Log.debug(this, "Sending SSH_MSG_CHANNEL_DATA id=" + channelid + " rid=" + remoteid + " len=" + len + " window=" + remotewindow.available()); } connection.sendMessage(msg.toByteArray(), true); } for (Enumeration e = listeners.elements(); e .hasMoreElements();) { (e.nextElement()).dataSent(Ssh2Channel.this, buf, offset, len); } } catch (IOException ex) { throw new SshException(ex, SshException.INTERNAL_ERROR); } finally { try { msg.close(); } catch (IOException e) { } } } void sendExtendedChannelData(byte[] buf, int offset, int len, int type) throws SshException { ByteArrayWriter msg = new ByteArrayWriter(len + 9); try { if (state != CHANNEL_OPEN) { throw new SshException("The channel is closed", SshException.CHANNEL_FAILURE); } if (len > 0) { msg.write(SSH_MSG_CHANNEL_EXTENDED_DATA); msg.writeInt(remoteid); msg.writeInt(type); msg.writeBinaryString(buf, offset, len); connection.sendMessage(msg.toByteArray(), true); } if (listeners != null) { for (int i = 0; i < listeners.size(); i++) { ((ChannelEventListener) listeners.elementAt(i)) .extendedDataReceived(Ssh2Channel.this, buf, offset, len, type); } } } catch (IOException ex) { throw new SshException(ex, SshException.INTERNAL_ERROR); } finally { try { msg.close(); } catch (IOException e) { } } } private void adjustWindow(long increment) throws SshException { ByteArrayWriter msg = new ByteArrayWriter(9); try { // Check that the channel isn't closing if (closing || isClosed()) return; msg.write(SSH_MSG_WINDOW_ADJUST); msg.writeInt(remoteid); msg.writeInt(increment); if (Log.isDebugEnabled()) { Log.debug(this, "Sending SSH_MSG_WINDOW_ADJUST id=" + channelid + " rid=" + remoteid + " window=" + localwindow.available() + " adjust=" + increment); } localwindow.adjust(increment); connection.sendMessage(msg.toByteArray(), true); } catch (IOException ex) { throw new SshException(ex, SshException.INTERNAL_ERROR); } finally { try { msg.close(); } catch (IOException e) { } } } /** * Sends a channel request. Many channels have extensions that are specific * to that particular channel type, an example of which is requesting a * pseudo terminal from an interactive session. * * @param requesttype * the name of the request, for example "pty-req" * @param wantreply * specifies whether the remote side should send a * success/failure message * @param requestdata * the request data * @return true if the request succeeded and wantreply=true, * otherwise false * @throws IOException */ public boolean sendRequest(String requesttype, boolean wantreply, byte[] requestdata) throws SshException { return sendRequest(requesttype, wantreply, requestdata, true); } /** * Sends a channel request. Many channels have extensions that are specific * to that particular channel type, an example of which is requesting a * pseudo terminal from an interactive session. * * @param requesttype * the name of the request, for example "pty-req" * @param wantreply * specifies whether the remote side should send a * success/failure message * @param requestdata * the request data * @param isActivity * @return true if the request succeeded and wantreply=true, * otherwise false * @throws IOException */ public boolean sendRequest(String requesttype, boolean wantreply, byte[] requestdata, boolean isActivity) throws SshException { synchronized (this) { ByteArrayWriter msg = new ByteArrayWriter(); try { msg.write(SSH_MSG_CHANNEL_REQUEST); msg.writeInt(remoteid); msg.writeString(requesttype); msg.writeBoolean(wantreply); if (requestdata != null) { msg.write(requestdata); } if (Log.isDebugEnabled()) { Log.debug(this, "Sending SSH_MSG_CHANNEL_REQUEST id=" + channelid + " rid=" + remoteid + " request=" + requesttype + " wantreply=" + wantreply); } connection.sendMessage(msg.toByteArray(), true); boolean result = false; if (wantreply) { SshMessage reply = processMessages(CHANNEL_REQUEST_MESSAGES); return reply.getMessageId() == SSH_MSG_CHANNEL_SUCCESS; } return result; } catch (IOException ex) { throw new SshException(ex, SshException.INTERNAL_ERROR); } finally { try { msg.close(); } catch (IOException e) { } } } } /** * Closes the channel. No data may be sent or receieved after this method * completes. */ public void close() { boolean performClose = false; ; synchronized (this) { if (!closing && state == CHANNEL_OPEN) { performClose = closing = true; } } if (performClose) { synchronized (listeners) { for (Enumeration e = listeners.elements(); e .hasMoreElements();) { (e.nextElement()).channelClosing(this); } } try { // Close the ChannelOutputStream out.close(!isLocalEOF); // Send our close message ByteArrayWriter msg = new ByteArrayWriter(5); msg.write(SSH_MSG_CHANNEL_CLOSE); msg.writeInt(remoteid); try { if (Log.isDebugEnabled()) { Log.debug(this, "Sending SSH_MSG_CHANNEL_CLOSE id=" + channelid + " rid=" + remoteid); } connection.sendMessage(msg.toByteArray(), true); } catch (SshException ex1) { if (Log.isDebugEnabled()) { Log.debug(this, "Exception attempting to send SSH_MSG_CHANNEL_CLOSE id=" + channelid + " rid=" + remoteid, ex1); } } finally { msg.close(); } this.state = CHANNEL_CLOSED; } catch (EOFException eof) { // Ignore this is the message store informing of close/eof } catch (SshIOException ex) { if (Log.isDebugEnabled()) { Log.debug(this, "SSH Exception during close reason=" + ex.getRealException().getReason() + " id=" + channelid + " rid=" + remoteid, ex.getRealException()); } // IO Error during close so the connection has dropped connection.transport.disconnect( TransportProtocol.CONNECTION_LOST, "IOException during channel close: " + ex.getMessage()); } catch (IOException ex) { if (Log.isDebugEnabled()) { Log.debug(this, "Exception during close id=" + channelid + " rid=" + remoteid, ex); } // IO Error during close so the connection has dropped connection.transport.disconnect( TransportProtocol.CONNECTION_LOST, "IOException during channel close: " + ex.getMessage()); } finally { checkCloseStatus(ms.isClosed()); } } } protected void checkCloseStatus(boolean remoteClosed) { if (state != CHANNEL_CLOSED) { close(); if (!remoteClosed) remoteClosed = (ms.hasMessage(CHANNEL_CLOSE_MESSAGES) != null); } if (remoteClosed) { synchronized (this) { if (!free) { synchronized (listeners) { for (Enumeration e = listeners .elements(); e.hasMoreElements();) { (e.nextElement()).channelClosed(this); } } connection.closeChannel(this); free = true; } } } } /** * This channel is equal to another channel if the channel id's are equal. * Please note that channel numbers may be reused so this method should only * be called on open channels. */ public boolean equals(Object obj) { if (obj instanceof Ssh2Channel) { return ((Ssh2Channel) obj).getChannelId() == channelid; } return false; } /** * Called when a channel request is received, by default this method sends a * failure message if the remote side requests a reply. Overidden methods * should ALWAYS call this superclass method. * * @param requesttype * the name of the request * @param wantreply * specifies whether the remote side requires a success/failure * message * @param requestdata * the request data * @throws IOException */ protected void channelRequest(String requesttype, boolean wantreply, byte[] requestdata) throws SshException { if (wantreply) { ByteArrayWriter msg = new ByteArrayWriter(); try { msg.write((byte) SSH_MSG_CHANNEL_FAILURE); msg.writeInt(remoteid); connection.sendMessage(msg.toByteArray(), true); } catch (IOException e) { throw new SshException(e, SshException.INTERNAL_ERROR); } finally { try { msg.close(); } catch (IOException e) { } } } } /** * Called when the remote side data stream is EOF, by default this method * does nothing * * @throws IOException */ protected void channelEOF() { } // / long transferedOut = 0; class ChannelOutputStream extends OutputStream { public void write(int b) throws IOException { write(new byte[] { (byte) b }, 0, 1); } public void write(byte[] buf, int offset, int len) throws IOException { try { long write; do { if (remotewindow.available() <= 0) { processMessages(WINDOW_ADJUST_MESSAGES); } synchronized (Ssh2Channel.this) { if (isLocalEOF) { throw new EOFException("The channel is EOF"); } if (isClosed() || closing) { throw new EOFException("The channel is closed"); } write = remotewindow.available() < remotewindow .getPacketSize() ? (remotewindow.available() < len ? remotewindow .available() : len) : (remotewindow.getPacketSize() < len ? remotewindow .getPacketSize() : len); if (write > 0) { sendChannelData(buf, offset, (int) write); remotewindow.consume((int) write); len -= write; offset += write; } } } while (len > 0); } catch (SshException ex) { throw new SshIOException(ex); } } public void close() throws IOException { close(!isClosed() && !isLocalEOF && !closing); } public void close(boolean sendEOF) throws IOException { if (sendEOF) { ByteArrayWriter msg = new ByteArrayWriter(5); msg.write(SSH_MSG_CHANNEL_EOF); msg.writeInt(remoteid); try { if (Log.isDebugEnabled()) { Log.debug(this, "Sending SSH_MSG_CHANNEL_EOF id=" + getChannelId() + " rid=" + remoteid); } connection.sendMessage(msg.toByteArray(), true); } catch (SshException ex) { throw new SshIOException(ex); } finally { msg.close(); } } isLocalEOF = true; if (isRemoteEOF) { Ssh2Channel.this.close(); } } } boolean isBlocking = false; class ChannelInputStream extends InputStream { int unread = 0; MessageObserver messagefilter; long transfered = 0; SshChannelMessage currentMessage = null; ChannelInputStream(MessageObserver messagefilter) { this.messagefilter = messagefilter; } void addMessage(int length, SshChannelMessage msg) { unread = length; currentMessage = msg; } /** * This method returns the number of SSH messages that can be read using * the read method without it blocking. In certain circumstances there * may be more messages available but this method cannot discover them * without blocking and so returns 0. */ public synchronized int available() throws IOException { try { if (unread == 0) { if (getMessageStore().hasMessage(messagefilter) != null) { processMessages(messagefilter); } } return unread; } catch (EOFException ex) { return -1; } catch (SshException ex) { throw new SshIOException(ex); } } public int read() throws IOException { byte[] b = new byte[1]; int ret = read(b, 0, 1); if (ret > 0) { return b[0] & 0xFF; } else { return -1; } } /* * public long skip(long len) throws IOException { * * // Less complicated skip to ensure correct read mechanism is used. * long count = 0; int r = 0; byte[] tmp = new byte[(int)len]; * while(count < len && r > -1) { r = read(tmp, 0, (int)(len - count)); * if(r > -1) { count += r; } } if(r==-1 && count==0) { throw new * EOFException(); } * * return count; } */ public synchronized int read(byte[] buf, int offset, int len) throws IOException { try { /* * if there is a message available then processMessages, if its * data then fills input buffer and sets unread */ if (available() == -1) { return -1; } while (unread <= 0 && !isClosed()) { processMessages(messagefilter); } int count = unread < len ? unread : len; if (count == 0 && isClosed()) { return -1; } currentMessage.read(buf, offset, count); localwindow.consume(count); unread -= count; if (System.getProperty("maverick.windowAdjustTest", "false") .equals("true") || (unread + localwindow.available()) < (localwindow .getInitialSize() / 2) && !isClosed() && !closing) { adjustWindow(localwindow.getInitialSize() - localwindow.available() - unread); } transfered += count; return count; } catch (SshException ex) { throw new SshIOException(ex); } catch (EOFException ex) { return -1; } } } static class DataWindow { long windowsize; long initialSize; int packetsize; DataWindow(long windowsize, int packetsize) { this.initialSize = windowsize; this.windowsize = windowsize; this.packetsize = packetsize; } int getPacketSize() { return packetsize; } long getInitialSize() { return initialSize; } void adjust(long count) { windowsize += count; } void consume(int count) { windowsize -= count; } long available() { return windowsize; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy